-
Notifications
You must be signed in to change notification settings - Fork 2
ООП Лекция 05. Классы. Конструкторы.
Компилятор по умолчанию предоставляет два конструктора(отвечают за инициализацию объекта): копирования и инициализации.
- При не определении явно конструктора - то устанавливается по умолчанию.
- Не имеет типа возврата.
<имя класса>::<имя класса/конструктора>([<список параметров>])[:<раздел инициализации>]
{
<тело конструктора>
};Пример:
A::A(int i): a(i), \\ можем инициализировать член объекта
cа(i), \\ можем -------- константный член объекта
sа(i), \\ не можем ------ члены класса
scа(i) \\ не можем ------ константный член класса
{
\\ Объект уже создан
a = i; \\ ОК!
сa = i; \\ Error!
sa = i; \\ OK!
csa = i; \\ Error!
};
Деструктор - это обратное конструктору, то есть освободить ресурсы и оставить связи непротиворечивыми, вызывается при уничтожении объекта.
Мои собственные заметки.
A obj1; // Конструктор инициализации
A obj2(obj1); // Конструктор копированияИз книги Герберта Шилдта "Самоучитель С++" про конструкторы копирования.
Когда мы передаем объект в какую-то функцию, например, void display(MyClass obj); создается копия этого объекта (и эта копия становится параметром функции). Когда при вызове функции создается копия объекта, обычный конструктор (то есть инициализации) не вызывается, вместо этого вызывается конструктор копий объекта.
Обычный конструктор используется для той или иной инициализации объекта, он не должен вызываться для копирования уже существующего объекта, так как такой вызов изменит содержимое объекта (передаст объект в начальном состоянии) а мы хотим передать функции текущее состояние объекта, то есть нужен конструктор копирования.
Важное замечание: когда мы передаем в функцию какой-то объект по ссылке, конструктор не вызывается. То есть в случае с void display(MyClass& obj); конструктор копирования бы не вызывался.
Когда мы возвращаем объект из какой-либо функции (не по ссылке), если этот объект создается внутри этой функции, тоже вызывается конструктор копирования. А если по ссылке... да еще и локальную переменную... ух)))
Если конструкторы, предоставляемые по умолчанию, объявить явно, то конструкторы, предоставляемые по умолчанию, не будут создаваться.
Мои собственные заметки.
Если мы не прописываем собственных конструкторов, мы спокойно можем создавать объект так:
Point p1;А если пропишем собственный конструктор инициализации, например,
Point(int x, int y, int z) : myX(x), myY(y), myZ(z) {}То можем создавать объекты только так:
Point p1(69, 228, 1488); // Так можно
Point p1; // Так уже нельзяХотя если мы добавим еще один конструктор инициализации:
Point() {}То можно будет использовать оба способа, описанных выше.
При вызове конструктора по умолчанию простые типы инициализируются базовыми значениями, а для объектов вызываются по умолчанию конструкторы этих объектов и их баз.
Конструктор по умолчанию можно задать явно:
class A{…};
class B{…};
class C: public A, public B
{
private:
A a;
B b;
public:
C(): B(), b(0), A(), a(0) {} // в разделе инициализации описывать только то, что должно произойти явно!
// C(): b(0), a(0) {} лучше так
};В данном примере А() и B() - неявное создание базовых объектов (поэтому писать не нужно, вызываются по умолчанию), а(0) и b(0) – инициализация полей с параметрами, значит их явно надо вызывать. Порядок, указанный тут (для конструкторов) роли не играет, важен порядок в описании класса.
Куруш поделился священным опытом.
A() {}; // Вот у нас раз
A(int x) {}; // Вот у нас два
// И вот теперь... вот так делать нельзя
A(int x, int y): A(), A(x) {}Делегировать конструктор можно только один раз. Помимо сего, при делегировании нельзя инициализировать другие члены класса (только уже в теле конструктора).
Делегирование - шняга с вызовом другого конструктора своего же класса.
Работает, начиная с с++11.
Напишем следующее чудо:
#include <initializer_list>
using namespace std;
class Complex
{
private:
double r, i;
public:
Complex() = default; // (1) Конструктор по умолчанию
// А default это нам самим лень что-то реализовывать
// Пусть за нас старается комплиятор
Complex(double r): Complex(r, 0.) {} // (2) Конструктор инициализации
Complex(int r) = delete; // (3) Запрещаем вызов конструктора инициализации
Complex(double r, double i); // (4) Конструктор инициализации
explicit Complex(const Complex& C); // (5) Конструктор копирования
Complex(Complex&& C); // (6) Конструктор переноса
Complex(initializer_list<double> list); // (7) Конструктор инициализации
// Просто методы-сеттеры
void setReal(int) = delete; // (8) Если что, в сигнатуре мы можем не указывать имя переменной...
void setReal(double z); // (9)
static Complex sum(const Complex&& c1, const Complex&& c2); // (10)
};Конструктор переноса - (6). Вместо глубокого копирования - захват параметров.
А теперь будем создавать объекты:
Complex a(); // я - Вызовем (1)
Complex b1; // я - Вызовем (1)
Complex b2{}; // я - В С++11 вызовем (7), в С++14 вызовем(1)
Complex b3 = {}; // н - (7)
Complex c1(1.); // я - В C++11 (2), в С++14 (7)
Complex c2{2.}; // я - В C++11 (7), в C++14 (2)
Complex c3 = {3.}; // н - (7)
Complex c4 = 4.; // н - (2)
Complex c5 = Complex(5.); // я - (2)
Complex d1(1., 2.); // я - (4)
Complex d2{2., 3.}; // я - всегда (7)
Complex d3 = {4., 5.}; // н - (7) (иначе (4))
Complex d4 = Complex(4., 5.); // я - (4)
Complex d5 = Complex({4., 5.}); // я - (7)
Complex e1(d1); // я - (5)
Complex e2{d2}; // я - (5)
Complex e3 = {d3}; // н - (5)
Complex e4 = Complex(d4); // я - (5)
Complex e5 = Complex({d5}); // я - (5)
Complex f1 = Complex::sum(d1, d2); // н - (6) вызов конструктора переносаГруппы явного и неявного вызова конструктора
Модификатор explicit запрещает неявный вызов конструктора.
- я – явный вызов
- н – неявный вызов
(1) b1 = {};
(2) c1 = {1.};
(3) c4 = 4.;
(4) d1 = {2., 3.};
(5) c5 = 5;
(1) и (2) - вызов конструктора, принимающего initializaton_list.
(2), (3), (4) – присваивание и включение механизма неявного привидения типа
(5) - Приведение к double и вызов конструктора.
Если нет конструктора, принимающего тип int, идет цепочка преобразований int-double- ... Любой конструктор - оператор приведения типа.
Когда используется конструктор Когда определяем объект (неважно какой, внешний или внутренний) Когда идёт приведение типов Когда передаем в метод по значению или возвращаем по значению Когда динамически выделяем память, используя оператор new (явный вызов). Подробнее о new и delete
Из книги Герберта Шилдта "Самоучитель С++" про динамическое выделение памяти.
С++ предоставляет два оператора для динамического выделения памяти: new и delete. Оператор new выделяет память и возвращает указатель на её начало. Оператор delete освобождает память, предварительно выделенную посредством new.
Мы можем выделить память под объекты. Динамически созданный объект ведет себя подобно любому другому объекту. Когда он создается, вызывается его конструктор (если он есть), а когда память освобождается, вызывается его деструктор.
Оператор new приводит к вызову конструктора, к созданию объекта. Возвращает типизированный указатель на объект типа <тип>.
new [(<ук-ль буфер>)] <тип> [[<размер массива>][{список}]|(<список параметров>)]Пример:
Complex *p = new Complex(1., 2.);Массив объектов – всегда плохо!
delete <указатель>Мои собственные заметки.
Когда выделили память под объект с помощью new, не нужно забывать освобождать ее с помощью delete... деструктор сам собой в этом случае не вызовется.
Любой объект должен быть уничтожен. Автоматически создается метод, уничтожающий объект в обратном порядке его создания.
Пример
class Array
{
private:
double *arr
int count;
public:
Array = default();
Array (initializer_list<double> list) // (1)
{
this->count = list.size();
if (!count)
arr = nullptr; // nullptr - объект
else
{
arr = new double[count];
int i = 0;
for (double elem:list)
arr[i++] = elem;
}
}
Array(const Array& mas) // (2)
{
count = mas.count;
if (!count)
arr = nullptr;
else
{
arr = new double[count];
for (i = 0; i < count; i++)
arr[i] = mas.arr[i]
}
}
Array (Array&& mas) // (3)
{
count = mas.count;
arr = mas.arr;
mas.arr = nullptr;
}
~Array() // (4)
{
delete[] arr;
}
};Комментарии к примеру:
- (1) - Конструктор, принимающий список.
- (2) - Конструктор копирования. Конструктор имеет доступ к приватным полям всех своих объектов.
- (3) - Конструктор переноса.
- (4) - Деструктор. Для того, чтобы отработали деструкторы объектов(при наличии и необходимости)
Конструкторы не наследуются. В производном классе необходимо переопределить конструкторы.
class MyArray: public Array
{
public:
using Array::Array; // Определяем в производном классе конструкторы базового класса
}В любом классе нужно явно определить
- Конструктор копирования
- Конструктор переноса
- Деструктор
- Оператор присваивания
- Конструкторы:
не могут быть const, static, volatile, virtual
могут быть cosntexpr (вычисляется на этапе компиляции), explicit (вызывается только явно)
Из Герберта Шилдта. Описатель volatile сообщает компилятору, что значение переменной может измениться, хотя в программе нет предложений, явным образом модифицирующих эту переменную, например, программа обработки прерываний от таймера может, получив адрес глобальной переменной, обновлять ее с каждым тактом таймера.
Конструкторы не наследуются.
- Деструкторы:
не могут быть const, static, volatile.
могут быть virtual.
Деструктор вызывается не явно. Деструктор уничтожает объект в порядке, обратном порядку создания.