- •1. Объектно-ориентированный подход в программировании.
- •1.1 Инкапсуляция
- •1.2 Наследование
- •1.3 Полиморфизм
- •2.1 Перегружаемые (overload) функции
- •2.2 Перегружаемые (overload) операторы
- •2.3 Объектные типы данных: структуры, объединения, классы (обзор)
- •2.4. Конструкторы и деструкторы
- •2.7 Производные классы
- •2.8 Встраиваемые функции
- •2.9 Присваивание объектов
- •2.10 Передача в функции и возвращение объекта
- •2.11 Конструктор копирования
- •2.12 Указатели и ссылки на объекты
- •3.1. Модификаторы наследования
- •3.2 Конструкторы при наследовании
- •3.3 Деструкторы при наследовании
- •3.4 Совместимость типов
- •4. Виртуальные функции.
- •4.1. Раннее и позднее связывание
- •4.2 Полиморфизм и виртуальные методы
- •4.3. Использование указателей на базовые классы при адресации объектов произвольных классов.
- •4.4 Абстрактный класс
- •5. Дружественные функции.
- •5.1 “Дружественные” (friend) функции
- •6. Шаблоны функций и классов
- •6.1 Шаблоны функций
- •6.2 Шаблоны классов
2.11 Конструктор копирования
В C++ методом передачи параметров по умолчанию является передача объектов по значению. При передаче объекта в функцию появляется новый объект. Когда работа функции, которой был передан объект, завершается, то удаляется копия аргумента. Как формируется копия объекта и вызывается ли деструктор объекта, когда удаляется его копия?
То, что вызывается деструктор копии, наверное, понятно, поскольку объект (копия объекта, передаваемого в качестве параметра) выходит из области видимости. Объект внутри функции - это побитовая копия передаваемого объекта, а это значит, что если объект содержит в себе, например, некоторый указатель на динамически выделенную область памяти, то при копировании создается объект, указывающий на ту же область памяти. И как только вызывается деструктор копии, где, как правило, принято высвобождать память, то высвобождается область памяти, на которую указывал объект-«оригинал», что приводит к разрушению исходного объекта.
Похожая проблема возникает и при использовании объекта в качестве возвращаемого значения. Для того чтобы функция могла возвращать объект, нужно: во-первых, объявить функцию так, чтобы ее возвращаемое значение имело тип класса, во-вторых, возвращать объект с помощью обычного оператора return. Однако если возвращаемый объект содержит деструктор, то в этом случае возникают похожие проблемы, связанные с « неожиданным » разрушением объекта.
Одним из способов обойти такого рода проблемы является создание особого типа конструкторов: конструкторов копирования. Конструктор копирования или конструктор копии позволяет точно определить порядок создания копии объекта.
Любой конструктор копирования имеет следующую форму.
имя_класса (const имя_класса & obj)
{
... // тело конструктора
}
Здесь obj - это ссылка на объект или адрес объекта. Конструктор копирования вызывается всякий раз, когда создается копия объекта. Мы уже рассмотрели два таких случая. Во-первых, при передаче объекта в качестве параметра функции. Во-вторых, при создании временного объекта тогда, когда функция в качестве возвращаемого значения использует объект. Есть еще один случай, когда полезен конструктор копирования, - это инициализация одного объекта другим.
class ClassName
{
public:
ClassName()
{
cout << 'Работа конструктора \n';
}
ClassName(const ClassName& obj)
{
cout << 'Работа конструктора копирования\n';
}
~ClassName()
{
cout << 'Работа деструктора \n';
}
};
main()
{
ClassName c1; // вызов конструктора
ClassName c2 = c1; // вызов конструктора копирования
}
Замечание. Конструктор копирования не влияет на операцию присваивания. С помощью конструктора копирования можно передавать объекты в качестве параметров функций и возвращать объекты. При этом количество вызовов конструкторов будет совпадать с количеством вызовов деструкторов, а поскольку процесс образования копий теперь стал контролируемым, существенно снизилась вероятность неожиданного разрушения объекта.
2.12 Указатели и ссылки на объекты
Доступ к членам объекта можно осуществлять и через указатель на объект. В этом случае обычно применяется операция « ->». Указатель на объект объявляется точно так же, как и указатель на переменную любого типа. Для получения адреса объекта перед ним необходим оператор « &».
main()
{
_3d A (2,3,4);
_3d *pA;
pA = &A;
double dM = pA->mod();
}
В C++ есть элемент, родственный указателю, - это ссылка. Ссылка является скрытым указателем и всегда работает в качестве другого (дополнительного) имени переменной.
Наиболее важное использование ссылки - передача ее в качестве параметра. Чтобы разобраться в том, как работает ссылка, рассмотрим для начала программу, использующую в качестве параметра указатель.
void ToZero(_3d *vec)
{
vec->set(0,0,0); // используется "->"
}
main()
{
_3d A(2,3,4);
ToZero(&A);
}
В C++ можно сделать то же самое без использования указателя с помощью параметра-ссылки.
void ToZero(_3d &vec)
{
vec.set(0,0,0); // используется "."
}
main()
{
_3d A(2,3,4);
ToZero(A);
}
При применении параметра-ссылки компилятор передает адрес переменной, используемой в качестве аргумента. При этом не только не нужно, но и неверно использовать оператор « *». Внутри функции компилятор использует переменную, на которую ссылается параметр-ссылка.
В отличие от передачи параметра по значению, использование ссылки предполагает, что функция, в которую передан данный параметр, будет манипулировать не копией передаваемого объекта, а непосредственно самим передаваемым объектом.
Параметры-ссылки имеют некоторые преимущества по сравнению с аналогичными альтернативными параметрами-указателями. Во-первых, нет необходимости получать и передавать в функцию адрес аргумента, адрес передается автоматически. Во-вторых, ссылки предлагают более понятный и элегантный интерфейс, чем неуклюжий механизм указателей.
Ссылки могут также использоваться в качестве возвращаемого значения функции. Обычно такой механизм применяется в сочетании со ссылкой-параметром или указателем this. Все эти случаи объединяет необходимость передачи измененного объекта через его адрес, а не путем возвращения копии такого объекта. Вспомним хотя бы пример с переопределением оператора «=», где в качестве возвращаемого значения выступала копия измененного оператором «=» объекта. Теперь мы знаем , что можно сделать проще и эффективнее, если возвращать ссылку на измененный объект.
_3d& _3d::operator = (_3d& b)
{
x = b.x;
y = b.y;
z = b.z;
return *this;
}
Использование в качестве возвращаемого значения ссылку гарантирует, что возвращаемым в качестве результата этой функции будет именно тот объект, который вызывал операцию «=», а не его копия.
3. Наследование на языке С++
