Лекция 12 Перегрузка операторов. Часть 2. Подбельский гл. 9.7, Страуструп гл. 11, Мейрс п. 11,15-17,19. Перегрузка копирующего конструктора T(const T&) и оператора присваивания operator= А зачем все это нужно? Если Вы этого не сделаете, то компилятор сам сгенерирует о.ф. operator=, которая будет выполнять почленное присваивание членов-данных одного объекта соответствующим членам другого. Для указателей такое присваивание означает побитовое копирование. Чем это грозит Вам в том случае, если для класса определены конструктор(-ы) с динамическим выделением памяти и деструктор с удалением этой памяти? len==4 sz==5 nameGame\0 len==5 sz==6 nameover\0! { Tbl t1("Game"), t2("over!"); t1t2
t2=t1; len==4 sz==5 nameGame\0 len==4 sz==5 name over\0! t1t2 На этот кусок памяти теперь ничто не указывает - это мусор! Tbl t3=t1; // эквивалентно Tbl t3(t1); // произойдет вызов копирующего конструктора Tbl(const Tbl&); Если Вы этого не сделаете, то компилятор языка С++ сам сгенерирует функцию конструктора Tbl(const Tbl&), которая будет выполнять почленное присваивание членов-данных объекта- образца соответствующим членам нового объекта. Для указателей такое присваивание означает побитовое копирование. len==4 sz==5 name t3 } // Конец блока (функции). Удаление локальных объектов t1, t2, t3 путем //вызова ~Tbl() деструктора для каждого из них. Трижды будет выполнена попытка // удалить с помощью delete[] из динамической памяти строку "Game", // что приведет к фатальной ошибке.
class Tbl { public:... Tbl(const Tbl&); // объявление копирующего к-ра в открытой части класса Tbl& operator= (const Tbl&); // объявление о.ф. в открытой части класса ~Tbl(){delete[] name;}//определение деструктора, удаляющего строку из дин. памяти private:... char *name; int sz, len; }; Tbl::Tbl(const Tbl& t) { len = t.len; name = new char [sz=t.sz]; // создаем новую строку в дин. памяти strcpy(name,t.name); // strcpy объявлена в } Tbl& Tbl::operator=(const Tbl& orig) { if (&orig != this) { // проверка на присваивание самому себе delete [] this->name; // удаляем старую строку this->name = new char [sz=orig.sz]; // создаем новую memcpy(this->name,orig.name,orig.sz); // копируем оригинал в новую строку len = strlen(name); // memcpy и strlen объявлены в } return *this; // возвращаем (ссылку на) объект, для которого была вызваны о.ф. } Пример 12.1: Перегрузка копирующего конструктора T(const T&) и оператора присваивания operator=
Как все это работает? len==4 sz==5 nameGame\0 len==5 sz==6 name over\0! { Tbl t1("Game"), t2("over!"); // сначала все также t1t2 t2=t1; // произойдет вызов перегруженного operator= len==4 sz==5 nameGame\0 len==4 sz==5 name over\0! t1t2 Удаляется в operator=() с помощью delete [] Tbl t3=t1; // эквивалентно Tbl t3(t1); произойдет вызов определенного Вами // копирующего конструктора Tbl(const Tbl&); len==4 sz==5 name t3 Game\0 Game } // Конец блока (функции). Удаление локальных объектов t1, t2, t3 путем //вызова деструктора ~Tbl()для каждого из них. Трижды будет выполнена попытка // удалить с помощью delete[] из динамической памяти строку "Game", // Но каждый раз будут удаляться разные объекты.Что и требовалось добиться.
Вопросы для самостоятельного изучения: Почему в качестве типа возвращаемого значения operator= плохо использовать void ? Почему тип возвращаемого значения operator=() Tbl &, а не const Tbl& ? Почему operator=() возвращает ссылку на объект, стоящий слева от =, а не справа ? Перегрузка операторов >> и << Придадим оператору > смысл извлечения данных-членов объектов класса Tbl из потока ввода. Чтобы сохранить естественный синтаксис операторов >> и << необходимо, чтобы объект класса Tbl являлся правым операндом. По этой причине мы обязаны в обоих случаях использовать внешнюю функцию (не член класса). Доступ к закрытым данным мы предоставим этим о.ф., объявив их друзьями класса (но можно иначе). Пример 12.2: Перегрузка оператора >> для извлечения данных-членов объектов класса Tbl из потока ввода. Для извлечения строки символов из потока ввода, воспользуемся getline() функцией-членом класса istream. Определение классов istream и ostream находятся в файле. #include class Tbl {... friend istream& operator>>(istream&, Tbl& ); // прототип друга может быть помещен friend ostream& operator>>(ostream&, const Tbl&); // в любой части класса... };
istream& operator>>(istream& in, Tbl& t) { delete [] t.name; t.name = new char [t.sz=81]; in.getline(t.name,81); t.len=strlen(t.name); } ostream& operator << (ostream& out, const Tbl& t) { out.width(2); out<<dec<<" SZ=="<<t.sz <<" LEN=="<<t.len <<" NAME=\""<<t.name<<"\" ptr=="<<hex<<int(t.name); } Определения операторных функций >> и << вне класса. Использование >> и << int main() { Tbl t1("Time"),t2(8),t3; cout<<t1<<endl<<t2<<endl<<t3<<endl; Tbl t4=t1; // эквивалентно Tbl t4(t1); произойдет вызов определенного Вами cout<<t4<<endl; // копирующего конструктора Tbl(const Tbl&); t3=" "; // сначала будет вызван Tbl(const char* ), а потом operator=() // после этого деструктор ~Tbl() cout<<t3<<endl; cout<<"Enter string:"<<endl; cin>>t2; cout<<t2<<endl; } // деструктор ~Tbl() будет вызван еще 4 раза
Будет напечатано: SZ==5 LEN==4 NAME="Time" ptr==22550 SZ==8 LEN==0 NAME="" ptr==22560 SZ==32 LEN==0 NAME="" ptr==22958 SZ==5 LEN==4 NAME="Time" ptr==22570 delete sz==9 len==8 SZ==9 LEN==8 NAME=" " ptr==23378 Enter string: qwerty SZ==81 LEN==6 NAME="qwerty" ptr==23960 delete sz==5 len==4 delete sz==9 len==8 delete sz==81 len==6 delete sz==5 len==4