
- •Введение. Принципы объектно-ориентированного программирования
- •Глава 1. Классы и объекты
- •1.1. Операция разрешения области видимости ::
- •1.2. Перечислимый тип
- •1.3. Модификатор const
- •1.4. Новый тип данных – ссылка &
- •Inline определение_функции
- •2. Определение класса. Сокрытие информации.
- •3. Объект.
- •4. Конструкторы и деструкторы
- •4.1.Назначение конструктора
- •4.2. Конструктор копирования
- •X::X(X&); // где X – имя класса
- •4.3. Деструктор
- •5. Неявный указатель this
- •6. Перегрузка операций
- •7. Примеры перегрузки некоторых операций
- •7.1. Перегрузка операции [ ]
- •7.2. Перегрузка операции ()
- •7.6. Перегрузка операции (тип)
- •8. Дружественность
- •Istream
- •10. Массивы объектов.
- •11. Функции- и классы-шаблоны
- •11.1 Функции-шаблоны (родовые функции)
- •11.2 Классы-шаблоны
- •12. Член-данные класса – объекты другого класса: агрегированные классы.
- •Глава 2. Наследование. Полиморфизм
- •1. Базовый и порожденный классы
- •2. Конструкторы порожденного класса
- •3. Стандартные преобразования при наследовании
- •4. Множественное наследование. Виртуальный базовый класс
- •4.1. Прямые базовые классы
- •4.2. Виртуальный базовый класс
- •5. Полиморфизм, раннее и позднее связывание, виртуальные функции
- •5.1 Раннее (статическое) и позднее (динамическое) связывание
- •5.2. Определение виртуальной функции
- •5.3. Чистая виртуальная функция и абстрактный класс
- •5.4. Правила определения виртуальных функций
- •5.5. Механизм позднего связывания
- •6. Библиотека fstream – работа с файлами
- •Глава 3. Библиотека стандартных шаблонов (бсш). Контейнеры
- •1. Контейнер. Структура бсш.
- •2. Контейнер Vector – динамический массив
- •Контейнер list – список
- •4. Контейнер Set – множество
- •Содержание
- •Глава 1. Классы и объекты
- •Глава 2. Наследование. Полиморфизм
- •Глава 3. Библиотека стандартных шаблонов (бсш). Контейнеры
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(“ФПМК”);
Особенности деструктора как функции:
он не имеет аргументов;
он не возвращает значения;
работает неявно для всех объектов при выходе из функций.
Заметим, что для объектов в динамической области при выходе из функции память надо освобождать явно. В нашем случае – это для объекта, заданного указателем s3.
s3 = new String (“ха-ха”);
delete s3;
При выполнении этого оператора память для объекта *s3 будет освобождаться в 3 этапа:
деструктором от слова «ха-ха»;
операцией delete от полей line и len;
стандартным освобождением от локальных переменных.
В заключение запишем класс 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, то такое присвоение было бы ошибочным, так как компилятор трактует его как присвоение одного кода символа другому коду, как в данном примере
‘в’=’х’;
что невозможно.