п 3. Конструкторы и деструкторы new. Аргументы









































Л2.Ч1.Конструкторы.Деструктор..ppt
- Количество слайдов: 41
п 3. Конструкторы и деструкторы new. Аргументы по умолчанию. В С++ можно не задавать фактические аргументы, компилятор их подставит автоматически, если в определении функции задано их некоторое значение. Например, void Line(int k, char s =’*’ ) // s - аргумент по // умолчанию { if (k<0) return; while ( k-- ) 10 * 50* 20! printf(“%c”, s); } Тогда возможны обращения Line(10); Line(50); Line(20, ’!’);
Можно задать несколько аргументов по умолчанию, но в списке они должны идти последними. Например, void Prx(double x, int k = 30, char s = ’*’) {. . . } Возможны обращения Prx(15. 5); Prx(x, 20); Prx(2*x, 10, ’+’); Неверно Prx(5, , ’!’);
п 3. 1. Назначение конструктора В С++ при определении переменных часто их сразу инициализируют. Например, int x = 5; Предположим, что при определении объекта String s; мы хотели бы проинициализировать его, например, пустой строкой: len = 0; line[0] = ’ ’. Для структур эта инициализация выполняется так: String s = {“”, 0}; Для объектов класса такая инициализация запрещена в силу принципа инкапсуляции.
Проблема Поэтому и возникает проблема: внутри описания класса инициализировать нельзя по синтаксису структуры, но и вне класса записать s. len = 0; s. line[0] = ’ ’; тоже нельзя, т. к. член-данные из части private недоступны. ( Заметим, что если определить их в части public, то их можно инициализировать как структуру, т. е. String s = {“”, 0}; )
Выход Инициализацию должна выполнять специальная член-функция класса.
Конструктор! Определение. Член-функция класса, предназначенная для инициализации член-данных класса при определении объектов класса, называется конструктором. Конструктор всегда имеет имя класса.
Конструктор в классе String В нашем примере можно записать такой конструктор String: : String(){ len = 0; line[0] = ’ ’; } (1) объявив его обязательно в теле класса следующим образом: String(); Тогда при определении объектов, например, String s 1, s 2; он будет всегда вызываться неявно, и выполнять инициализацию объектов. Так конструктор не имеет аргументов, то он называется конструктором по умолчанию.
Несколько конструкторов В классе можно задать не один конструктор, а несколько. Для класса String можно задать конструктор с аргументом, аналогичный функции Fill String: : String(const char * s) (2) { for( len = 0; line[len] = s[len]; len++); } Тогда объекты можно определить таким образом String s 1, s 2(“Иванов”), s 3 = String(“Петров”); работает конструктор с аргументом (2) работает конструктор по умолчанию (1) Для объекта s 3 конструктор задается явно(но так конструктор используется редко).
Заметим, что в классе должен быть один конструктор по умолчанию и один или несколько с аргументами.
Особенности конструктора, как функции: 1. Главная - конструктор не имеет возвращаемого значения (даже void), так как его единственное назначение – инициализировать собственные член- данные объекта; 2. Конструктор имеет имя класса; 3. Конструктор всегда работает неявно при определении объектов класса.
Недостаток определенного класса String - это то, что он берет для каждого объекта 259 (257)байтов памяти, хотя фактически использует меньше. Изменим определение класса String таким образом class String{ char *line; int len; public: . . }; Тогда конструкторы надо определить иначе, т. к. кроме инициализации значений член-данных , они должны брать память в динамической области для поля line.
Другие конструкторы В классе объявим 2 конструктора String(int l = 80); //с аргументом по // умолчанию String (const char *); //с аргументом // строкой
и определим их вне класса String: : String(int l) // l=80 – не повторять! (3) {line = new char [l]; len=0; line[0]=’ ’; } String: : String(const char * s) (2’) {line = new char [strlen(s)+1]; // для нуль-кода for(len=0; line[len] = s[len]; len++); }
Пример использования String s 1(10), s 2, s 3(“без слов”); конструктор (3) конструктор (2’) аргумент задан l=10 аргумент по умолчанию l=80 l=8+1=9
Замечание В классе должен быть или конструктор по умолчанию без аргументов вида (1), или конструктор с аргументом по умолчанию вида (3). В противном случае, следующее определение String ss; вызовет сообщение о двусмысленности ‘Ambiguity between ‘String: : String()’ and ‘String: : String(int)’ - ‘Двусмысленность между String() и String( int )’
п 3. 2 Конструктор копирования New: тип данных & - ссылка задает альтернативное имя переменной (псевдоним) Формат тип & имя_2 = имя_1; Ссылка при объявлении всегда должна быть проинициализирована и в дальнейшем ее изменить нельзя.
Пример int a, b=4; 5 4 ? 4 int &x = a; // x - новое имя переменной a Одна и та же ячейка имеет 2 имени a и x! Поэтому, если x = b; то это равносильно действию a=b x++; // равносильно a++
Ссылка & Если вывести printf(“%u %u” , &x, &a); то выведется адрес одной и той же ячейки.
Ссылка & и указатель * Сравним ссылку с понятием указателя int a; int & x = a; // x - ссылка int *y = &a; // y - указатель Истинны следующие логические выражения *y = = x Значение одной и той же ячейки a y = = &x Адрес одной и той же ячейки a
Определение ссылки Таким образом, ссылку можно рассматривать как постоянный (константный) указатель, который всегда разадресован, т. е. к нему не нужно применять операцию *.
Этот тип данных введен по таким причинам: 1) Для того, чтобы избегать копирования фактического аргумента в формальный аргумент функции; особенно это важно для сложных типов данных - структур, классов; Формальный аргумент просто становится другим именем фактического. 2) Для того, чтобы иметь возможность менять значения фактических аргументов
Классический пример Требуется поменять значение 2 -х переменных a и b void swap( int a, int b) void swap( int *a, int *b) void swap( int &a, int &b) { int c; { int c; c = a; c = *a; c = a; a = b; *a = *b; a = b; b = c; *b = c; b = c; } } void main() { int x = 4, y = 7; swap(x, y); swap( &x, &y); swap(x, y); printf(“x = %d y = % d); // вывод x = 4 y = 7 // вывод x = 7 y = 4 } Значения x и y поменяются: не поменяются! поменяются за счет a и b просто новые передачи указателей! имена ячеек x и y!
Инициализация значением другой переменной В С++ кроме инициализации константным значением int x = 5; . . . x++; . . . используется и такая инициализация данных int y = x; // инициализация одного данного // значением другого
В классе String подобная инициализация может привести к ошибкам. Рассмотрим - почему. Пусть заданы определения String s(“паровоз”); . . . String r = s; // определение объекта r и // инициализация его значением // объекта s r. Index(4) = ‘х’ ; r. Index(6) = ‘д’; // изменим на пароход Если вывести теперь объекты s и r s. Print(); r. Print(); то увидим, что выведется пароход в обоих случаях. Это плохо.
Разберемся, почему это происходит При определении объекта String s(“паровоз”); String: : String(const char * s) (2’) работает конструктор, { line = new char [strlen(s)+1]; // для нуль-кода for( len=0; line[len] = s[len]; len++); } который возьмет память в динамической области 8 байтов line len s: 7 п а р о в о з куча! и адрес первого запишет в поле s. line. Затем цикл for занесет в поле *line слово паровоз и одновременно определит len = 7.
При определении объекта r String r = s; // или String r(s); компилятор просто выполняет копирование полей r. line = s. line и r. len = s. len line len х д s: 7 п а р о в о з r: 7 А значит поле r. line будет показывать на ту же динамическую область! И при выполнении операторов r. Index(4) = ‘х’ ; r. Index(6) = ‘д’; изменятся оба объекта.
Что неграмотно и недопустимо !
Поэтому для инициализации одного объекта другим надо определить специальный конструктор копирования, заголовок которого в общем случае имеет вид X : : X( X& ); // где X - имя класса В классе String его можно задать следующим образом const String: : String(String &s) { line = new char[ s. len+1 ]; for (len = 0; line[ len ] = s. line[ len ]; len++); } объявив его в классе String(String &);
Тогда инициализация String: : String(String &s) String r = s; { line = new char[ s. len+1 ]; for (len = 0; line[ len ] = s. line[ len ]; len++); } выполнится грамотно. line len s: 7 п а р о в о з куча х д Конструктор копирования возьмет для объекта r п а р о в о з line len r: 5 1 7 6 4 3 2 новую динамическую память длиной s. len + 1 байтов. И цикл for затем запишет из объекта s в поле r. line слово паровоз, в поле r. len длину 7. При выполнении операторов r. Index(4) = ‘х’ ; r. Index(6) = ‘д’; значение s. line теперь не изменится !
Все верно Оператор s. Print(); // выведет ‘паровоз’ а r. Print(); // выведет ‘пароход’
Замечание Конструктор копирования кроме рассмотренной инициализации работает также • при передаче значений фактических аргументов-объектов в функцию и • при возврате результата-объекта из функции.
п 3. 3. Деструктор В языке С++ одним из важных моментов является освобождение памяти, занятой переменными, при выходе из функции. Рассмотрим пример. Определена функция void F() { int k; String s 1(20), s 2(“ФПМК”), *s 3; s 3= new String (“ТГУ”); }
При выходе из функции освобождается память для локальных объектов, т. е. k, s 1, s 2, s 3. Но рассмотрим внимательнее, как это будет реализовано. При выходе куча k: Пустая строка из 20 байтов line len s 1: . . . 4 байта Эта память line len s 2: Ф П М К будет 4 “брошена!” line len new s 3: 3 Не экономно! Т Г У конструктор 2’
Для того, чтобы при выходе из функций динамическая память, которая берется конструкторами объектов, освобождалась автоматически, надо задать специальную член-функцию деструктор.
Деструктор Определение. Деструктор - это член функция класса, предназначенная для освобождения динамической памяти, занимаемой член-данными класса, при выходе из функций. Деструктор имеет формат ~ имя_класса(){…}
Пример Для класса String его можно определить таким образом ~ String(){delete [ ] line; } В этом случае при выходе из области видимости функции F() память для объектов s 1, s 2, которую брал конструктор для поля line, будет освобождена. Заданный деструктор это будет делать по умолчанию.
Память по операции Работает стандартное освобождение 3) Стандартно от ячейки s 3 delete s 3; памяти от локальных данных при выходе из функции будет освобождена в 3 этапа: при выходе из функции куча k: Пустая строка из 20 байтов line len s 1: . . . 4 байта line len Работает s 2: 4 Ф П М К деструктор line len new s 3: 3 Т Г У Динамическую память, занятую объектом, заданным через указатель s 3, надо освобождать явно операцией 1) деструктором delete s 3; 2) операцией delete
Особенности деструктора как функции: • он не имеет аргументов; • он не возвращает значения; • работает неявно для всех объектов при выходе из функций. Замечание. Работу деструктора можно “увидеть”, если в деструкторе задать какой- либо вывод. Например, ~String(){ printf(“n. Работает деструктор класса String”); delete [ ] line; }
class String{ char *line; int len; public: String(int l=80); // конструктор по // умолчанию String(const char *); // конструктор с // аргументом String(String &); // конструктор // копирования ~String(){ delete [] line; } // деструктор void Print(){ printf(“%s”, line; } int Len() { return len; }; char & Index( int ); void Fill( const char* ); };
char & Index (int) Определим функцию Index за классом char & String: : Index( int i) { if(i<0 || i>=len) { printf(”n Индекс за пределами “); exit(0); } return line[i]; } Тип возвращаемого значения char & - ссылка возвращает не просто значение символа, а ссылку на ячейку, где он находится. Это и позволяет выполнить присвоение вида r. Index (4) = ’х’;
Возвращаемый тип char Если определить тип возвращаемого значения просто char, то присвоение вида r. Index (4) = ’х’; (*) было бы ошибочным, так как функция вернет значение символа и компилятор будет трактовать оператор (*), как присвоение одного кода символа другому коду, как в данном примере ‘в’=’х’; что невозможно. В других операциях в этом случае символ использоваться может, кроме присвоения ему нового значения.

