Освой самостоятельно С++ за 21 день.
Шрифт:
20: {
21: itsWidth = 5;
22: itsLength = 10;
23: }
24:
25: Rectangle::Rectangle (int width, int length)
26: {
27: itsWidth = width;
28: itsLength = length;
29: }
30:
31: int main
32: {
33: Rectangle Rect1;
34: cout << "Rect1 width: " << Rect1.GetWidth << endl;
35: cout << "Rect1 length: " << Rect1.GetLength << endl;
36:
37: int aWidth, aLength;
38: cout << "Enter a width: ";
39: cin >> aWidth;
40: cout << "\nEnter a length: ";
41: cin >> aLength;
42:
43: Rectangle Rect2(aWidth, aLength);
44: cout << "\nRect2 width: " << Rect2.GetWidth << endl;
45: cout << "Rect2 length: " << Rect2.GetLength << endl;
46: return 0;
47: }
Результат:
Rect1 width: 5
Rect1 length: 10
Enter a width: 20
Enter a length: 50
Rect2 width: 20
Rect2 length: 50
Анализ:
Как и при использовании других перегруженных функций, компилятор выбирает нужное объявление конструктора, основываясь на числе и типе параметров.
Инициализация объектов
До сих пор переменные-члены объектов задавались прямо в теле конструктора. Выполнение конструктора происходит в два этапа: инициализация и выполнение тела конструктора.
Большинство переменных может быть задано на любом из этих этапов: как во время инициализации, так и во время выполнения конструктора. Но логически правильнее, а зачастую и эффективнее, инициализировать переменные-члены во время инициализации конструктора. В следующем примере показана инициализация переменных-членов:
CAT: // имя конструктора и список параметров
itsAge(5), // инициализация списка
itsWeigth(8)
{} // тело конструктора
После скобки закрытия списка параметров конструктора ставится двоеточие. Затем перечисляются имена переменных-членов. Пара круглых скобок со значением за именем переменной используется для инициализации этой переменной. Если инициализируется сразу несколько переменных, то они должны быть отделены запятыми. В листинге 10.4 показана инициализация переменных конструкторов, взятых из листинга 10.3. В данном примере инициализация переменных используется вместо присвоения им значений в теле конструктора.
Листинг 10.4. Фрагмент программного кода с инициализацией переменных-членов
1: Rectangle::Rectangle:
2: itsWidth(5),
3: itsLength(10)
4: {
5: }
6:
7: Rectangle::Rectangle (int width, int length):
8: itsWidth(width),
9: itsLength(length)
10: {
11: }
Результат: Отсутствует
Анализ: Некоторые переменные можно только инициализировать и нельзя присваивать им значения: например, в случае использования ссылок и констант. Безусловно, переменной-члену можно присвоить значение прямо в теле конструктора, но для упрощения программы лучше по возможности устанавливать значения переменных-членов на этапе инициализации конструктора.
Конструктор-копировщик
Помимо конструктора и деструктора, компилятор по умолчанию предоставляет также конструктор-копировщик, который вызывается всякий раз, когда нужно создать копию объекта.
Когда объект передается как значение либо в функцию, либо из функции в виде возврата, всегда создается его временная копия. Если в программе обрабатывается объект, созданный пользователем, то для выполнения этих операций вызывается конструктор- копировщик класса, как было показано на предыдущем занятии в листинге 9.6.
Все копировщики принимают только один параметр — ссылку на объект в том же классе. Разумно будет сделать эту ссылку константной, так как конструктор не должен изменять передаваемый в него объект. Например:
CAT(const CAT & theCat);
В данном случае конструктор CAT принимает константную ссылку на объект класса CAT. Цель использования конструктора-копировщика состоит в создании копии объекта theCat.
Копировщик, заданный компилятором по умолчанию, просто копирует все переменные-члены из указанного в параметре объекта в переменные-члены нового объекта. Такое копирование называется поверхностным; и, хотя оно подходит для большинства случаев, могут возникнуть серьезные проблемы, если переменные-члены окажутся указателями на ячейки динамической памяти.
Поверхностное копирование создает несколько переменных-членов в разных объектах, которые ссылаются на одни и те же ячейки памяти. Глубинное копирование переписывает значения переменных по новым адресам.
Например, класс CAT содержит переменную-член theCat, которая указывает на ячейку в области динамической памяти, где сохранено некоторое целочисленное значение. Копировщик по умолчанию скопирует переменную theCat из старого класса CAT в переменную theCat в новом классе CAT. При этом оба объекта будут указывать на одну и ту же ячейку памяти (рис. 10.1).
Рис. 10.1. Использование копировщика, заданного по умолчанию
Проблемы могут возникнуть, если программа выйдет за область видимости одного из классов CAT. Как уже отмечалось при изучении указателей, назначение деструктора состоит в том, чтобы очищать память от ненужных объектов. Если деструктор исходного класса CAT очистит свои ячейки памяти, а объекты нового класса CAT все так же будут ссылаться на эти ячейки, то над программной нависнет смертельная опасность. Эта проблема проиллюстрирована на рис. 10.2.