Слайд 12. Перегрузка операции =
Если объект использует динамическую
область, то для него
надо перегрузить
операцию ‘= ‘ - присвоение.
Рассмотрим почему.
Пусть заданы 2 объекта
String
s1, s2(“ФПМК”);
...
s1 = s2;
Слайд 2Картина присвоения напоминает
ситуацию с инициализацией (даже хуже):
до присвоения
s1 = s2;
после
присвоения
Так как компилятор выполняет
простое копирование
s1.line = s2.line;
s1.len = s2.len
Нельзя
разбрасываться
памятью!
и оба объекта используют
одну и ту же память
и
Слайд 3Это недопустимо по следующим причинам:
1. Память в 80 байтов у
объекта s1 будет
«брошена» (считаться занятой);
2. объекты s1 и s2 будут
использовать одну и ту
же динамическую память по указателю line, что
приведет к тому, что любое изменение в поле
line объекта s1 приведет к изменению поля
line объекта s2 и наоборот;
3. при выходе из функции деструктор будет
пытаться дважды освободить одну и ту же
динамическую память: это фатальная ошибка!
Слайд 4В классах, где используется
динамическая память, операция ‘=’
обязательно перегружается.
Слайд 5Пример перегрузки операции = для класса String
String & String ::
operator =(String &s)
{
{ delete [ ] line;// Важно
освободить // динамическую память у *this
line = new char [(len = s.len) + 1]; // сразу // определим поле len
strcpy(line, s.line);
}
return *this;
}
if( this != &s) // на случай присвоения s = s
Слайд 6Теперь присвоение s1 = s2 будет
выполняться грамотно.
0
s1:
line
len
\0
...
Пустая строка из 80 байтов
s2:
4
line
len
Ф
П
М
К
\0
delete [ ] line; // 1)
line = new char [(len = s.len)+1]; // 2)
strcpy( line, s.line); // 3)
Ф
П
М
К
\0
1)
2)
3)
4
Основные действия
операции =
куча
Все неприятности исчезнут!
Слайд 7Конструктор копирования и
операция присвоения =
String s(“УРА!”), r(“ФПМК”);
String p =
s;// работает конструктор копирования
…
r = s; // работает перегруженная
операция =
Слайд 8Отличие операции = и
конструктора копирования
Оператор = выполняет 3 действия :
1.
Освобождает динамическую память у левого объекта ( её могло быть
меньше или больше, чем у правого);
2. берёт новую динамическую память размером её у правого объекта;
3. копирует поля правого объекта в поля левого.
Конструктор копирования выполняет 2 действия:
1. Берет динамическую память для левого
объекта размером её у правого;
2. копирует поля левого объекта в поля правого.
Конструктор копирования не может
выполнить освобождение памяти
у левого объекта, так как
у него её ещё и НЕ БЫЛО!
Слайд 9Если хотя бы один конструктор берёт динамическую память, то обязательно
возникают:
Деструктор
Конструктор копирования
Оператор =
Слайд 103. Перегрузка операции ()
Если объект - матрица, то для
обращения к ее
элементам нельзя перегрузить [][].
В этом случае можно
использовать перегрузку
операции ().
class Matrix{ int **a, m, n;
public:
Matrix( int m1 = 1, int n1 = 1, int t = 0, int d = 10);
// конструктор с аргументами по умолчанию
~Matrix();
void Show(); // вывод матрицы
int & operator()(int, int);
...
};
Слайд 11Конструктор
Matrix::Matrix(int mm, int nn, int t, int d)
// mm – строк, nn – столбцов, d -
диапозон
// t != 0 – генерировать случайные числа
{int i, j; m = mm; n = nn;
a = new int * [m];
for( i = 0; i < m; i++)
a[i] = new int [n];
if(t)
for(i = 0; i < m; i++)
for(j = 0; j < n; j++)
a[i][j] = rand()%d;
}
Слайд 12Перегрузка ()
int & Matrix :: operator()(int i, int j)
{ if
(i < 0 || i >= m || j
0 || j >= n)
{puts("\n Значения индексов недопустимы. Выход.”);exit(1);}
return a[ i ][ j ];
}
Возвращаемое значение - ссылка int & - для того,
чтобы иметь возможность
менять значения элементов матрицы.
Слайд 13Пример использования
void main()
{ randomize();
Matrix A(3,4),B(3,3,1);
// A не инициализируется
// случайными числами,
// B – инициализируется
for ( int i = 0; i<3; i++)
B(i, i) = 1; // занесение 1 на главную диагональ
puts("\nB:“);
B.Show();
...
}
Слайд 14Замечание
Операция () - единственная,
которая может иметь
произвольное количество
аргументов( в частности
Слайд 15Пример
void Matrix:: operator()()
// очистить матрицу
{ int i, j;
for (
i = 0; i
0; j a[i][j] = 0;
}
Использование
Matrix B(5, 5);
B();
Слайд 16Задание:
определить деструктор класса Matrix
Matrix :: ~Matrix()
{ int i;
for(i =
0; i
Слайд 174.Перегрузки операций + и +=
a += b; a = a
+ b;
a – = b; a = a – b;
a *=
b; a = a * b;
a /= b; a = a / b;
Слайд 184.Перегрузки операций + и +=
Ранее был рассмотрен пример перегрузки
операции
+=, меняющей первый операнд, то
есть *this. В классе String определим
операцию
+, которая не меняет ни первого операнда, ни
второго, как это принято при сложении базовых
типов данных.
Например, когда мы выполняем операцию
a+b,
то результат не записывается ни в a, ни в b,
если мы не выполним соответствующего
присвоения (например, a =a+b, b=a+b, c= a+b).
Слайд 19Перегрузка +
Определение операции + может быть задано таким
образом
String
String :: operator + (const String &s)
{ String z( len
+ s.len + 1); // определим локальную строку // суммарной длины, пустую
strcpy( z.line, line); // перепишем в нее строку // первого операнда
strcat(z.line,s.line); // прибавим строку второго // операнда
z.len = strlen( z.line ); // определим длину результата
return z; // работает конструктор // копирования результата, // затем деструктор разрушает локальный объект z
}
Поэтому нельзя вернуть ссылку на локальный объект!
&
Работает конструктор с аргументом по умолчанию
Слайд 20Пример использования
void main()
{ String s1(“Объект ”), s2(“класса “),
s3(“ String”);
String
s4 = s1 + s2 + s3; // работают 2
// операции ‘+’ и конструктор копирования
s4.Print(); // вывод “Объект класса String”
}
Слайд 215. Перегрузка операции ++
Одноместная операция ++ перегружается
только в префиксной форме
(++i),
но может использоваться в постфиксном
виде (i++, компилятор дает об
этом
предупреждение), работая, однако, префиксно.
++i; и i++; будут действовать одинаково
и
x = ++i * 5 и x = i++ * 5 тоже одинаково!
Слайд 225. Перегрузка операции ++
Например, i = 5;
x=i++; // x =
5, i = 6, сначала x = i, затем i++
x=++i;
// x = 6, i = 6, сначала ++ i, затем x = i
Слайд 23Перегрузка ++ в классе String
Пример.
Операция ++ увеличивает коды символов
на 1.
String
String :: operator ++ ()
{ for(int i = 0; i
< len; i++)
line[ i ]++;
return *this;
}
Слайд 24Использование:
void main()
{ String d(“12345*678”);
++d;
d.Print(); // d = ”23456+789”
}
Аналогично
перегружается операция --.
Слайд 256. Перегрузка операции (тип)
Операция (тип) используется для
преобразования базовых типов данных.
Например,
если мы хотим узнать код символа
char s = ‘*’,
то сделать это можно оператором
int k = (int) s;
Есть еще такая форма записи операции (тип)
тип(выражение).
Например,
float a = 3.76, b = 0.5, c = 1.22, d = 7;
int k = int (a*b - c*d / b);
В данных примерах эти преобразования
действуют и неявно (по умолчанию).
// int k = s;
// int k = a*b - c*d / b;
Слайд 26Вернемся к классу String
Пусть задан такой фрагмент программы
String s1, s2(“Солнце!”);
char *str = ”Жарко!”;
Как отреагирует компилятор на следующие
присвоения?
s1 = str;
// char * ->String?
допустимо: преобразование из char* в String
выполняет конструктор String(char *) и в поле
s1.line перепишется строка «Жарко!», поле
s1. len =6.
Слайд 27Итак, преобразование
конструктор(базовый тип)
базовый тип -------->абстрактный
выполняет
конструктор абстрактного
класса с аргументом базового типа
(если есть) по умолчанию.
Слайд 28Рассмотрим присвоение наоборот
str = s2; // ошибка: компилятор не знает,
какое поле из объекта s2 требуется
переписать в строку
str.
Другими словами, что понимается под
преобразованием
String -> char*
Поэтому, если требуется выполнять это
преобразование явно
str = (char *)s2;
или неявно
str = s2;
то надо определить, что понимается под
этим преобразованием.
И это знаем только
мы!
Слайд 29Перегрузка операции преобразования
имеет общий вид
operator тип ()
{…}
В нашем случае, например,
её можно
определить следующим образом
String :: operator char *()
{return line;
}
Слайд 30И присвоение
str = s2; // неявное String -> char *
или
str = (char *)s2; // явное преобразование
не вызовет ошибочного
сообщения
компилятора,
и *str = ”Солнце!”
Недостаток:
использование ограниченно из-за фиксированной
длины поля line в объекте s2, а также из-за того,
что указатели str и s2.line указывают
на одну и ту же динамическую память “Солнце!”
Слайд 31Другие определения (char *)
String :: operator char*()
{ String *t
= new String ( *this );
return t->line;}
или
String
:: operator char *()
{ char *t = new char [ len+1 ];
strcpy(t, line);
return t;
}
Работает конструктор
копирования String(String &)
Недостаток: поля t->len и t->line будут брошены!
Так эффективнее всего.
Слайд 32А так вообще нельзя!
String :: operator char*()
{ String t
( *this ); // действует конструктор
// копирования
return t.line;}
t – локальный объект и при выходе он будет полностью разрушен и деструктором класса String от t->line в куче,
и стандартным деструктором от t.len, t.line!
Слайд 33String -> int
Это преобразование из String в int можно
определить таким
образом
String :: operator int()
{return len;
}
Тогда можно выполнить присвоениe
int k =
s2; // работает operator int()
// k = 7; т.к. String s2(“Солнце!”); и s2.len = 7
Слайд 34Итак
Преобразование
operator тип
абстрактный тип -------> базовый тип
задается
специальным оператором
(тип).
Слайд 35Другое преобразование String ->int
Определим более полезное преобразование из
String в int:
преобразование числа-строки в форму целого
числа.
String :: operator int()
{ int k
= 0, i;
for(i = 0; i k = k*10 + line[i] - ‘0’;
return k;
}
Слайд 36Пример использования:
String d(“12345”); int m;
m = d;
m = 12345,
выполнено преобразование
числа-строки во внутреннюю форму
целого числа
Слайд 37Преобразование float -> Complex
Определим классе Complex
class Complex { float
re, im;
public:
Complex( float a, float b)
{ re = a; im = b;}
Complex( float d = 0)
{ re = im = d;}
Complex operator +(Complex &);
...
};
Слайд 38float -> Complex
Будут справедливы такие действия
Complex c1, c2(5, 3); //
c1 = 0 +i*0
// c2 = 5 + i*3
float x = 3.3, y;
c1 = x; // c1 = 3.3 + i *3.3
Работает конструктор Complex( float ),
который определит и мнимую и вещественную
части комплексного числа равной x, т.е.
с1(3.3 + i*3.3)
Преобразование float -> Complex по умолчанию
выполняет конструктор Complex ( float d=0)
Слайд 39Преобразование Complex -> float
Определим классе Complex
class Complex { float
re, im;
public:
Complex( float a, float b)
{ re = a; im = b;}
Complex( float d = 0)
{ re = im = d;}
Complex operator +(Complex &);
...
};
Слайд 40float -> Complex
Будут справедливы такие действия
Complex c1, c2(5, 3); //
c1 = 0 +i*0
// c2 = 5 + i*3
float x = 3.3, y;
c1 = x; // c1 = 3.3 + i *3.3
Работает конструктор Complex( float ),
который определит и мнимую и вещественную
части комплексного числа равной x, т.е.
с1(3.3 + i*3.3)
Преобразование float -> Complex по умолчанию
выполняет конструктор Complex ( float d=0)
Слайд 41Complex -> float
Поэтому обратное преобразование
Complex -> float надо определить пользователю,
например,
таким образом
Complex :: operator float()
{ return re;}
Тогда оператор
y =
c2;
будет верным и y = c2.re = 5,
которую вернет
operator float ()
Слайд 42Преобразование
абстрактный тип1 ---------> абстрактный тип2
Можно определить и такое преобразование
operator (абстрактный тип2)
абстрактный тип1 --------->
абстрактный тип2
Например, определим необычное преобразование
String -> Complex
String :: operator Complex()
{ Complex z(len); // действует конструктор Complex(float)
return z; }
Тогда следующий фрагмент кода будет выглядеть
совершенно нормально
String s(“Маша ела кашу”); Complex c; // c = 0 + i*0
c = s; // c = 13 + i*13
Complex( float d = 0)
{ re = im = d;}
Слайд 43Задание
Определите столь же необычное, но
полезное преобразование
Complex -> String !
Слайд 44Особенности перегрузки операции (тип)
Нет аргументов (операция чаще
используется неявно) и нет
возвращаемого
значения (даже void), т.к.
тип - это и есть возвращаемое значение;
2.
В теле операции обязательно должен
быть оператор return со значением, тип
которого является типом преобразования.