2. Перегрузка операции = Если объект использует
2. Перегрузка операции = Если объект использует динамическую область, то для него надо перегрузить операцию ‘= ‘ - присвоение. Рассмотрим почему. Пусть заданы 2 объекта String s 1, s 2(“ФПМК”); . . . s 1 = s 2;
Картина присвоения напоминает ситуацию с инициализацией (даже хуже): до присвоения Так компилятор выполняет простое копирование s 1. line = s 2. line; s 1 = s 2; s 1. len = s 2. len после присвоения и оба объекта используют одну и ту же память Ох, как жаль эти 80 байтов!
Это недопустимо по следующим причинам: 1. Память в 80 байтов у объекта s 1 будет «брошена» (считаться занятой); 2. объекты s 1 и s 2 будут использовать одну и ту же динамическую память по указателю line, что приведет к тому, что любое изменение в поле line объекта s 1 приведет к изменению поля line объекта s 2 и наоборот; 3. при выходе из функции деструктор будет пытаться дважды освободить одну и ту же динамическую память: это фатальная ошибка!
В классах, где используется динамическая память, операция ‘=’ обязательно перегружается.
Пример перегрузки операции = для класса String & String : : operator =(String &s) { if( this != &s) // на случай присвоения s = s { delete [ ] line; // Важно освободить // динамическую память у *this line = new char [(len = s. len) + 1]; // сразу // определим поле len strcpy(line, s. line); } return *this; }
Теперь присвоение s 1 = s 2 будет выполняться грамотно. delete [ ] line; // 1) Основные действия line = new char [(len = s. len)+1]; // 2) операции = strcpy( line, s. line); // 3) line len Пустая строка из 80 байтов s 1: 4 0 . . . 1) Ф П М К 2) line len 3) s 2: 4 Ф П М К куча Все неприятности исчезнут!
Конструктор копирования и операция присвоения = String s(“УРА!”), r(“ФПМК”); String p = s; // работает конструктор копирования … r = s; // работает перегруженная операция =
Отличие операции = и конструктора копирования Оператор = выполняет 3 действия : 1. Освобождает динамическую память у левого объекта ( её могло быть меньше или больше, чем у правого); 2. берёт новую динамическую копирования не может Конструктор память размером выполнить освобождение памяти её у правого объекта; у левого объекта, так как 3. копирует поля правого объекта в поля левого. у него её ещё и НЕ БЫЛО! Конструктор копирования выполняет 2 действия: 1. Берет динамическую память для левого объекта размером её у правого; 2. копирует поля левого объекта в поля правого.
3. Перегрузка операции () Если объект - матрица, то для обращения к ее элементам нельзя перегрузить [][]. В этом случае можно использовать перегрузку операции (). class Matrix{ int **a, m, n; public: Matrix( int m 1 = 1, int n 1 = 1, int t = 0, int d = 10); // конструктор с аргументами по умолчанию ~Matrix(); void Show(); // вывод матрицы int & operator()(int, int); . . . };
Конструктор Matrix: : Matrix(int mm, int nn, int t, int d) // mm – строк, nn – столбцов, d - диапозон // t != 0 – генерировать случайные числа {m = mm; n = nn; int i, j; a = new int * [m]; for( i = 0; i
Перегрузка () 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 & - для того, чтобы иметь возможность менять значения элементов матрицы.
Пример использования 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("n. B: “); B. Show(); . . . }
Замечание Операция () - единственная, которая может иметь произвольное количество аргументов( в частности 0).
Пример void Matrix: : operator()() // очистить матрицу { int i, j; for ( i = 0; i
Задание: определить деструктор класса Matrix : : ~Matrix() { int i; for(i = 0; i
4. Перегрузки операций + и += В п. 5 был рассмотрен пример перегрузки операции +=, меняющей первый операнд, то есть *this. В классе String определим операцию +, которая не меняет ни первого операнда, ни второго, как это принято при сложении базовых типов данных. Например, когда мы выполняем операцию a+b, то результат не записывается ни в a, ни в b, если мы не выполним соответствующего присвоения (например, a =a+b, b=a+b, c= a+b).
Перегрузка + Определение операции + может быть задано таким образом Работает конструктор с аргументом по умолчанию 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 } Поэтому нельзя вернуть ссылку на локальный объект!
Пример использования void main() { String s 1(“Объект ”), s 2(“класса “), s 3(“ String”); String s 4 = s 1 + s 2 + s 3; // работают 2 // операции ‘+’ и конструктор копирования s 4. Print(); // вывод “Объект класса String” }
5. Перегрузка операции ++ Одноместная операция ++ перегружается только в префиксной форме (++i), но может использоваться в постфиксном виде (i++, компилятор дает об этом предупреждение), работая, однако, префиксно. ++i; и i++; будут действовать одинаково и x = ++i * 5 и x = i++ * 5 тоже одинаково!
Перегрузка ++ в классе String Пример. Операция ++ увеличивает коды символов на 1. String : : operator ++ () { for(int i = 0; i < len; i++) line[ i ]++; return *this; }
Использование: void main() { String d(“ 12345*678”); ++d; d. Print(); // d = ” 23456+789” } Аналогично перегружается операция --.
6. Перегрузка операции (тип) Операция (тип) используется для преобразования базовых типов данных. Например, если мы хотим узнать код символа char s = ‘*’, то сделать это можно оператором int k = (int) s; // int k = s; Есть еще такая форма записи операции (тип) тип(выражение). данных примерах эти преобразования В Например, действуют и неявно (по умолчанию). float a = 3. 76, b = 0. 5, c = 1. 22, d = 7; int k = int (a*b - c*d / b); // int k = a*b - c*d / b;
Вернемся к классу String Пусть задан такой фрагмент программы String s 1, s 2(“Солнце!”); char *str = ”Жарко!”; Как отреагирует компилятор на следующие присвоения? s 1 = str; // char * ->String? допустимо: преобразование из char* в String выполняет конструктор String(char *) и в поле s 1. line перепишется строка «Жарко!» , поле s 1. len =6.
Итак, преобразование конструктор(базовый тип) базовый тип ---->абстрактный выполняет конструктор абстрактного класса с аргументом базового типа (если есть) по умолчанию.
Рассмотрим присвоение наоборот str = s 2; // ошибка: компилятор не знает, какое поле из объекта s 2 требуется переписать в строку str. Другими словами, что понимается под преобразованием String -> char* Поэтому, если требуется выполнять это преобразование явно str = (char *)s 2; И это знаем только или неявно мы! str = s 2; то надо определить, что понимается под этим преобразованием.
Перегрузка операции преобразования имеет общий вид operator тип () {…} В нашем случае, например, её можно определить следующим образом String : : operator char *() {return line; }
И присвоение str = s 2; // неявное String -> char * или str = (char *)s 2; // явное преобразование не вызовет ошибочного сообщения компилятора, и *str = ”Солнце!” Недостаток: использование ограниченно из-за фиксированной длины поля line в объекте s 2, а также из-за того, что указатели str и s 2. line указывают на одну и ту же динамическую память “Солнце!”
Другие определения (char *) Работает конструктор String : : operator char*() копирования String(String &) { String *t = new String ( *this ); return t->line; } Недостаток: поля t->len и t->line будут брошены! или String : : operator char *() { char *t = new char [ len+1 ]; strcpy(t, line); return t; Так эффективнее всего. }
А так вообще нельзя! String : : operator char*() { String t ( *this ); // действует конструктор // копирования return t. line; } t – локальный объект и при выходе он будет полностью разрушен и деструктором класса String от t->line в куче, и стандартным деструктором от t. len, t. line!
String -> int Это преобразование из String в int можно определить таким образом String : : operator int() {return len; } Тогда можно выполнить присвоениe int k = s 2; // работает operator int() // k = 7; т. к. String s 2(“Солнце!”); и s 2. len = 7
Итак Преобразование operator тип абстрактный тип -------> базовый тип задается специальным оператором (тип).
Другое преобразование String ->int Определим более полезное преобразование из String в int: преобразование числа-строки в форму целого числа. String : : operator int() { int k = 0, i; for(i = 0; i
Пример использования: String d(“ 12345”); int m; m = d; m = 12345, выполнено преобразование числа-строки во внутреннюю форму целого числа
Преобразование 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 &); . . . };
float -> Complex Будут справедливы такие действия Complex c 1, c 2(5, 3); // c 1 = 0 +i*0 // c 2 = 5 + i*3 float x = 3. 3, y; c 1 = x; // c 1 = 3. 3 + i *3. 3 Работает конструктор Complex( float ), который определит и мнимую и вещественную части комплексного числа равной x, т. е. с1(3. 3 + i*3. 3) Преобразование float -> Complex по умолчанию выполняет конструктор Complex ( float d=0)
Преобразование 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 &); . . . };
float -> Complex Будут справедливы такие действия Complex c 1, c 2(5, 3); // c 1 = 0 +i*0 // c 2 = 5 + i*3 float x = 3. 3, y; c 1 = x; // c 1 = 3. 3 + i *3. 3 Работает конструктор Complex( float ), который определит и мнимую и вещественную части комплексного числа равной x, т. е. с1(3. 3 + i*3. 3) Преобразование float -> Complex по умолчанию выполняет конструктор Complex ( float d=0)
Complex -> float Поэтому обратное преобразование Complex -> float надо определить пользователю, например, таким образом Complex : : operator float() { return re; } Тогда оператор y = c 2; будет верным и y = c 2. re = 5, которую вернет operator float ()
Преобразование абстрактный тип 1 -----> абстрактный тип 2 Можно определить и такое преобразование operator (абстрактный тип 2) абстрактный тип 1 -----> абстрактный тип 2 Например, определим необычное преобразование String -> Complex Complex( float d = 0) String : : operator Complex() { re = im = d; } { Complex z(len); // действует конструктор Complex(float) return z; } Тогда следующий фрагмент кода будет выглядеть совершенно нормально String s(“Маша ела кашу”); Complex c; // c = 0 + i*0 c = s; // c = 13 + i*13 Но это Экзотика? ! наши классы!
Задание Определите столь же необычное, но полезное преобразование Complex -> String !
Особенности перегрузки операции (тип) 1. Нет аргументов (операция чаще используется неявно) и нет возвращаемого значения (даже void), т. к. тип - это и есть возвращаемое значение; 2. В теле операции обязательно должен быть оператор return со значением, тип которого является типом преобразования.
Л4.Ч1.Перегрузка=,(),тип.ppt
- Количество слайдов: 41

