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

Скачать презентацию п 3. Конструкторы и деструкторы new. Аргументы Скачать презентацию п 3. Конструкторы и деструкторы new. Аргументы

Л2.Ч1.Конструкторы.Деструктор..ppt

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

> п 3. Конструкторы и деструкторы new. Аргументы по умолчанию.  В С++ можно п 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. Назначение конструктора В С++ при определении переменных часто п 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 В нашем примере можно записать такой конструктор 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. Главная - конструктор не Особенности конструктора, как функции: 1. Главная - конструктор не имеет возвращаемого значения (даже void), так как его единственное назначение – инициализировать собственные член- данные объекта; 2. Конструктор имеет имя класса; 3. Конструктор всегда работает неявно при определении объектов класса.

> Недостаток определенного   класса String - это то, что он берет для Недостаток определенного класса String - это то, что он берет для каждого объекта 259 (257)байтов памяти, хотя фактически использует меньше. Изменим определение класса String таким образом class String{ char *line; int len; public: . . }; Тогда конструкторы надо определить иначе, т. к. кроме инициализации значений член-данных , они должны брать память в динамической области для поля line.

>  Другие конструкторы В классе объявим 2 конструктора  String(int l = 80); Другие конструкторы В классе объявим 2 конструктора String(int l = 80); //с аргументом по // умолчанию String (const char *); //с аргументом // строкой

>и определим их вне класса String: : String(int l) // l=80 – не повторять! и определим их вне класса 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(“без слов”); конструктор Пример использования 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: тип данных & - ссылка задает альтернативное имя п 3. 2 Конструктор копирования New: тип данных & - ссылка задает альтернативное имя переменной (псевдоним) Формат тип & имя_2 = имя_1; Ссылка при объявлении всегда должна быть проинициализирована и в дальнейшем ее изменить нельзя.

>     Пример int a, b=4;     5 Пример 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); то выведется Ссылка & Если вывести printf(“%u %u” , &x, &a); то выведется адрес одной и той же ячейки.

> Ссылка & и указатель * Сравним ссылку с понятием указателя int a; Ссылка & и указатель * Сравним ссылку с понятием указателя int a; int & x = a; // x - ссылка int *y = &a; // y - указатель Истинны следующие логические выражения *y = = x Значение одной и той же ячейки a y = = &x Адрес одной и той же ячейки a

> Определение ссылки Таким образом, ссылку можно рассматривать как постоянный (константный) указатель, который всегда Определение ссылки Таким образом, ссылку можно рассматривать как постоянный (константный) указатель, который всегда разадресован, т. е. к нему не нужно применять операцию *.

>Этот тип данных введен по таким причинам:  1) Для того, чтобы избегать копирования Этот тип данных введен по таким причинам: 1) Для того, чтобы избегать копирования фактического аргумента в формальный аргумент функции; особенно это важно для сложных типов данных - структур, классов; Формальный аргумент просто становится другим именем фактического. 2) Для того, чтобы иметь возможность менять значения фактических аргументов

>   Классический пример  Требуется поменять значение 2 -х переменных a и Классический пример Требуется поменять значение 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 = Инициализация значением другой переменной В С++ кроме инициализации константным значением int x = 5; . . . x++; . . . используется и такая инициализация данных int y = x; // инициализация одного данного // значением другого

>В классе String подобная инициализация может привести к ошибкам. Рассмотрим - почему. Пусть заданы В классе String подобная инициализация может привести к ошибкам. Рассмотрим - почему. Пусть заданы определения String s(“паровоз”); . . . String r = s; // определение объекта r и // инициализация его значением // объекта s r. Index(4) = ‘х’ ; r. Index(6) = ‘д’; // изменим на пароход Если вывести теперь объекты s и r s. Print(); r. Print(); то увидим, что выведется пароход в обоих случаях. Это плохо.

>  Разберемся, почему это   происходит При определении объекта String s(“паровоз”); Разберемся, почему это происходит При определении объекта 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 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 Тогда инициализация 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(); Все верно Оператор s. Print(); // выведет ‘паровоз’ а r. Print(); // выведет ‘пароход’

>  Замечание Конструктор копирования кроме рассмотренной инициализации работает также • при передаче значений Замечание Конструктор копирования кроме рассмотренной инициализации работает также • при передаче значений фактических аргументов-объектов в функцию и • при возврате результата-объекта из функции.

>  п 3. 3. Деструктор В языке С++ одним из важных моментов является п 3. 3. Деструктор В языке С++ одним из важных моментов является освобождение памяти, занятой переменными, при выходе из функции. Рассмотрим пример. Определена функция void F() { int k; String s 1(20), s 2(“ФПМК”), *s 3; s 3= new String (“ТГУ”); }

> При выходе из функции освобождается память для локальных объектов, т. е. k, s При выходе из функции освобождается память для локальных объектов, т. е. 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 Пример Для класса String его можно определить таким образом ~ String(){delete [ ] line; } В этом случае при выходе из области видимости функции F() память для объектов s 1, s 2, которую брал конструктор для поля line, будет освобождена. Заданный деструктор это будет делать по умолчанию.

>      Память по операции Работает стандартное освобождение 3) Стандартно Память по операции Работает стандартное освобождение 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 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 & 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, то присвоение вида Возвращаемый тип char Если определить тип возвращаемого значения просто char, то присвоение вида r. Index (4) = ’х’; (*) было бы ошибочным, так как функция вернет значение символа и компилятор будет трактовать оператор (*), как присвоение одного кода символа другому коду, как в данном примере ‘в’=’х’; что невозможно. В других операциях в этом случае символ использоваться может, кроме присвоения ему нового значения.