Скачать презентацию Оператор присваивания. Указатель this. #include  #include «matrix Скачать презентацию Оператор присваивания. Указатель this. #include #include «matrix

L11 Оператор присваивания УказательTHIS.ppt

  • Количество слайдов: 9

Оператор присваивания. Указатель this. #include <iostream> #include Оператор присваивания. Указатель this. #include #include "matrix 2. hpp" using namespace std; const int NOFCOLS = 100; const int NOFROWS = 200; void foo(matrix &m) { matrix tmp; // Внутри функции объявляется объект tmp типа matrix. // Поскольку число строк и столбцов при этом не указывается, конструктор создает массив размером 2 x 2. tmp = m; // Объекту tmp приравнивается массив а (функции foo передается ссылка на а). cout << tmp (2, 10) << endl; } int main (){ int i, j; matrix a(NOFROWS, NOFCOLS); for(i = 0; i < NOFROWS; i++) for(j = 0; j < NOFCOLS; j++) a(i, j) = i * j; foo(a); cout << a(2, 10) << endl; }

 • Размеры обоих массивов не совпадают (tmp имеет размеры 2 x 2, в • Размеры обоих массивов не совпадают (tmp имеет размеры 2 x 2, в то время как а — 200 х 100), но оператор присваивания по умолчанию копирует объекты бит за битом. • После присваивания переменная nrows_ объекта tmp будет равна 200, переменная ncols_ — 100, a mini_ будет указывать на область памяти, где хранятся переменные массива а. • Т. е. tmp становится после присваивания точной копией массива а. • Деструктор уничтожит объект tmp перед выходом из функции, а вместе с ним освободит и память, на которую указывает mini_. • Но память, используемая массивами tmp и а, — общая! Уничтожатся оба объекта и попытки использования массива а в основной программе приведут, скорее всего, к ее аварийному завершению. • Чтобы устранить этот недостаток, нужно, так определить оператор присваивания, чтобы для копии объекта выделялась другая область памяти. Тогда деструктор уничтожит копию, но не тронет основной объект, передаваемый функции.

 • • Оператор присваивания имеет много общего с конструктором копирования (Но это не • • Оператор присваивания имеет много общего с конструктором копирования (Но это не одно и то же: конструктор копирования используется при создании объекта, а оператор присваивания приравнивает существующий объект другому. ) Первый вариант операторной функции для него может выглядеть так: void matrix: : operator=(matrix &mc) { delete [ ] mini_; nrows_ = mc. rget (); ncols_ = mc. cget (); mini_ = new double [nrows_ * ncols_]; for(int i = 0; i < nrows_; i++) for(int j = 0: j < ncols_; j++) mini_[i * ncols_ + j] = mc(i, j): } • Как видим, сначала освобождается память, занимаемая объектом (в который будем копировать), затем оператор узнает размеры копируемого массива, выделяет столько памяти, чтобы он смог там поместиться, и копирует элементы массива — строка за строкой.

 • Казалось бы, оператор присваивания делает все, что от него требуется. • Но • Казалось бы, оператор присваивания делает все, что от него требуется. • Но давайте представим, что в программе объект а копируется сам в себя (а=а; ). Хоть смысла в этом немного, но такая операция разрешена, а значит, должна выполняться. • Рассматриваемый оператор присваивания сначала освобождает память объекта, а потом выделяет для него другую ее область. Это значит, что в случае присваивания а=а; копирование элементов массива а будет идти из уже освобожденной области памяти. • Язык C++ устроен так, что освобожденная память может в любой момент быть занята чем-то другим. Получается, что копирование идет из того места, где объекта уже нет: присваивание оборачивается уничтожением! • Ясно, что избежать уничтожения при «самоприсваивании» можно, лишь имея возможность отличать себя от других. Для этого каждый объект располагает специальным указателем «на себя» — this. Указатель this не объявляется, но все происходит так, как будто он незримо присутствует в классе.

 • Например, в классе matrix наряду с размерами массива и указателем на область • Например, в классе matrix наряду с размерами массива и указателем на область памяти как бы присутствует еще и указатель this: class matrix { public: … private: matrix *this; //невидимый указатель на объект int nrows_: int ncols_: double *mini_; }: • Этот указатель this, в отличие от nrows_ и ncols_, возникает автоматически при создании объекта и хранит его адрес. Поэтому this можно использовать, чтобы отличить «свой» объект от «чужого» в новом варианте оператора присваивания.

 • Проверка на самоприсваивание в операторе = void matrix: : operator=(matrix &mc) { • Проверка на самоприсваивание в операторе = void matrix: : operator=(matrix &mc) { if (this != &mc) { delete [ ] mini_; nrows_ = mc. rget (); ncols_ = mc. cget (); mini_=new double [nrows_ * ncols_]; for (int i = 0; i < nrows_; i++) for(int j = 0; j < ncols_; j++) mini_[i * ncols_ + j] = mc(i, j); } } • Смысл условия if (this != &mc) прост: если указатель на текущий объект this равен адресу копируемого объекта, то «это ты сам и есть» , и тогда вообще ничего делать не надо — остается только покинуть операторную функцию оператора присваивания. • Указатель this в операторной функции позволяет избежать cамоприсваивания. Но и ему не дано сделать оператор присваивания идеальным.

 • Представим себе, что в программе появилась цепочка операций присваивания: matrix a, b, • Представим себе, что в программе появилась цепочка операций присваивания: matrix a, b, c; с = b = a; • • • С точки зрения языка C++ такая запись правильна и в соответствии со свойствами оператора присваивания выполняется справа налево: сначала объект b становится равным а, а затем и с становится равным b. Если C++ оперирует стандартными объектами, такими как целые числа или строки (тип string), присваивание происходит правильно и в нужном порядке. Но оператор присваивания, операторная функция для которого представлена выше, ожидает ссылку на объект, но сам ничего не возвращает. Поэтому цепочка операций присваивания, выполняемая с его помощью, прервется уже на первом шаге: при выполнении инструкции b=а операторная функция ничего не возвращает, а значит, объект с не сможет стать равным b. Совершенно ясно, что цепочка операций присваивания возможна, лишь когда оператор присваивания возвращает и принимает объекты одного типа. Если наш оператор принимает ссылку на объект, то и возвращать он должен тоже ссылку!

matrix & operator=(matrix & mc){ if(this != &mc){ delete [ ] m; nrows = matrix & operator=(matrix & mc){ if(this != &mc){ delete [ ] m; nrows = mc. rget(); ncols = mc. cget(); m = new double [nrows * ncols]; For (int i = 0; i < nrows; i++) for( int j = 0; j < ncols; j++) m [i * ncols + j] = mc(i. j); } return *this; } • • • Посмотрим, как выполняется с новым оператором цепочка операций присваивания с = b = а. Сначала вызывается оператор присваивания объекта b, то есть this указывает в этом случае именно на b. После присваивания оператор возвращает ссылку на b — (return *this) оператору присваивания с. Оператор =, как и положено, принимает ссылку, копирует данные из объекта b в объект с и снова возвращает ссылку — на этот раз на объект с и, поскольку дальнейших операций присваивания нет, эта ссылка исчезает в пустоте. Заметим, что возврат ссылок, так же как их передача, выглядят в языке C++ довольно странно. Ведь return (*this) означает возврат объекта, а не ссылки на него (this — указатель, а оператор * превращает указатель в сам объект). Но поскольку указано, что функция возвращает ссылку, то объект испытывает еще одно невидимое превращение.

 • • Внутреннее устройство объектов Собственные функции и данные хранятся в памяти компьютера • • Внутреннее устройство объектов Собственные функции и данные хранятся в памяти компьютера отдельно. Объектов, принадлежащих одному классу, может быть много, а набор собственных функций у них один. Собственной функции передается при вызове указатель this, с помощью которого она получает доступ к данным именно этого объекта. Даже функции, не имеющей явных параметров, тайно передается указатель this, и, например, функция cget класса matrix на самом деле устроена так: int matrix: : cget(matrix *this){ return this->ncols; } • Указатель this чаще всего остается незамеченным, но иногда, как, например, в операторе присваивания, его приходится использовать явно.