Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Lektsii_OOP.doc
Скачиваний:
2
Добавлен:
01.03.2025
Размер:
4 Mб
Скачать

4.2. Конструктор копирования

В С++ кроме инициализации значением

int x = 5;

x++;

используется инициализация одного данного значением другого

int y = x;

В классе String подобная инициализация может привести к ошибкам. Рассмотрим почему.

Пусть заданы определения

String s(«паровоз»);

String r = s;

r.Index(4) = ‘х’ ; r.Index(6) = ‘д’;

Если вывести теперь объекты s и r

s.Print();

r.Print();

то увидим, что выведется пароход в обоих случаях.

Разберемся, почему это происходит.

При определении объекта s выделилась память для член-данных len и line, затем конструктор взял динамическую память для слова “паровоз”, в поле line записал адрес, а затем в динамическую область – слово «паровоз». При объявлении объекта r выделяется память только для поля len и указателя line, память для значения line не берется. При инициализации String r = s; выполняется присвоение r.len = s.len и r.line = s.line (говорят, что операция ‘=’ предопределена в компиляторе, как копирование). А последнее означает, что s.line и r.line будут показывать на одну и ту же динамическую область. Поэтому изменение в объекте r приводит к изменению объекта s.

Что неграмотно и недопустимо!

Поэтому для инициализации одного объекта другим надо задать специальный конструктор копирования, заголовок которого имеет вид

X::X(X&); // где X – имя класса

В классе String его можно задать следующим образом

String:: String(String & s)

{ line = new char[s.len + 1];

for (len = 0; line[len] != ‘\0’; line[len] = s[len], len++);

line[len] = ‘\0’;}

Тогда инициализация

String r = s; // или String r(s);

выполнится грамотно.

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

4.3. Деструктор

В языке С++ одним из самых важных моментов является освобождение памяти, занятой переменными, при выходе из функции.

Рассмотрим пример. Определена функция

void F( )

{ int k;

String s1(20), s2(«ФПМК»), *s3;

s3 = new String («ха-ха»);

}

При выходе из функции освобождается память для локальных объектов, то есть k, s1, s2, s3. Но рассмотрим внимательнее, как это будет реализовано.

Таким образом, память в динамической области, связанная с объектами s1 и s2, будет считаться занятой («брошенной»). Чтобы этого не происходило, надо задать специальную функцию деструктор.

Определение. Деструктор – это член-функция класса, предназначенная для освобождения динамической памяти, занимаемой член-данными класса, при выходе из функций. Деструктор имеет формат

~ имя_класса( ) {}

Для класса String его можно определить таким образом

~ String( ) {delete [ ] line;}

В этом случае при выходе из области видимости функции F( ) память для объектов s1, s2, которую брал конструктор, будет освобождена. Заданный деструктор это будет делать по умолчанию.

int k;

String s1(20), s2(“ФПМК”);

Особенности деструктора как функции:

  1. он не имеет аргументов;

  2. он не возвращает значения;

  3. работает неявно для всех объектов при выходе из функций.

Заметим, что для объектов в динамической области при выходе из функции память надо освобождать явно. В нашем случае – это для объекта, заданного указателем s3.

s3 = new String (“ха-ха”);

delete s3;

При выполнении этого оператора память для объекта *s3 будет освобождаться в 3 этапа:

  1. деструктором от слова «ха-ха»;

  2. операцией delete от полей line и len;

  3. стандартным освобождением от локальных переменных.

В заключение запишем класс String с конструкторами и деструктором:

Class String{ char * line; int len;

public:

String(int l = 80); // конструктор по умолчанию

String(const char *); // конструктор с аргументом

String(String &); // конструктор копирования

~String() {delete line;} // деструктор

void Print() {cout << ”\nСтрока: “ << line;}

int Length() {return len;};

char & Index(int );

void Fill(const char*);

};

Определим функцию Index( ) за классом.

char & String:: Index(int i)

{if (i < 0 || i >= n) {cout << «\n Индекс за пределами строки»;

return line[0];

}

return line[i];}

Тип возвращаемого значения char & – ссылка, то есть возвращается не просто значение символа, а ссылка на ячейку, где он находится. Это и позволяет выполнить присвоение вида

r.Index(4) = ’х’;

Если бы тип был просто char, то такое присвоение было бы ошибочным, так как компилятор трактует его как присвоение одного кода символа другому коду, как в данном примере

‘в’=’х’;

что невозможно.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]