double arrow

Деструктор

Конструктор.

КОНСТРУКТОРЫ И ДЕСТРУКТОРЫ

Лекция №3

Конструкторы и деструкторы

Недостатком рассмотренных ранее классов является отсутствие автоматической инициализации создаваемых объектов. Для каждого вновь создаваемого объекта необходимо было вызвать функцию типа set (как для класса complex) либо явным образом присваивать значения данным объекта. Однако для инициализации объектов класса в его определение можно явно включить специальную компонентную функцию, называемую конструктором. Формат определения конструктора следующий:

имя_класса(список_формальных_параметров){операторы_тела_конструктора}

Конструктор – это функция, которая вызывается при создании объекта

Имя этой компонентной функции по правилам языка С++ должно совпадать с именем класса.

Пример 1.

сomplex (double re1=0.0, double im1=0.0) {re=re1; im=im1;}

Итак, создавая некоторый объект, его необходимо проинициализировать. Для этой цели C++ предоставляет функцию-член, которая называется конструктором. Конструктор класса вызывается всякий раз, когда создается объект его класса. Конструктор имеет то же имя, что и класс, членом которого он является, и не имеет возвращаемого значения.

Заметим, что программист не должен писать код, вызывающий конструктор класса. Всю необходимую работу выполняет компилятор. Конструктор вызывается тогда, когда создается объект его класса. Объект, в свою очередь, создается при выполнении оператора, объявляющего этот объект. Таким образом, в C++ оператор объявления переменной является выполняемым оператором.

Для глобальных объектов конструктор вызывается тогда, когда начинается выполнение программы. Для локальных объектов конструктор вызывается всякий раз при выполнении оператора, объявляющего переменную.

Динамическое выделение памяти для объекта создает необходимость освобождения этой памяти при уничтожении объекта. Например, если объект формируется как локальный внутри блока, то целесообразно, чтобы при выходе из блока, когда уже объект перестает существовать, выделенная для него память была возвращена. Желательно, чтобы освобождение памяти происходило автоматически. Такую возможность обеспечивает специальный компонент класса – деструкторкласса. Его формат:

~имя_класса(){операторы_тела_деструктора}

Имя деструктора совпадает с именем его класса, но предваряется символом “~” (тильда).

Функцией-членом, выполняющей действия, обратные конструктору, является деструктор. Эта функция-член вызывается при удалении объекта. Деструктор обычно выполняет работу по освобождению памяти, занятой объектом. Он имеет то же имя, что и класс, которому он принадлежит, с предшествующим символом ~ и не имеет возвращаемого значения.

Деструктор класса вызывается в момент удаления объекта. Это означает, что для глобальных объектов он вызывается при завершении программы, а для локальных – когда они выходят из области видимости. Заметим, что невозможно получить указатели на конструктор и деструктор.

Пример 2.

classDot

{

public:

Dot(){cout<<"Constructor \n";} // конструктор

~Dot(){cout<<"Destructor \n";} // деструктор

};

intmain()

{

Dot A; // создание объекта

}

Эта программа выводит на экран следующее:

Constructor

Destructor

Пример 3.

#include<iostream.h>

classAnyClass

{

intvar;

public:

AnyClass(); // конструктор

~AnyClass(); // деструктор

voidShow();

};

AnyClass :: AnyClass()

{

cout<<"В конструкторе \n";

}

AnyClass ::~AnyClass()

{

cout<<"В деструкторе \n";

}

voidAnyClass::Show()

{

cout<<var;

}

intmain()

{

AnyClass ob; // создание объекта

ob.Show();

}

Обычно конструктор содержит параметры, которые позволяют при построении объекта задать ему некоторые аргументы. Если нужно проинициализировать переменные класса, используется конструктор с параметрами.

Пример 4.

#include<iostream.h>

classAnyClass

{

inta, b;

public:

AnyClass(int x, int y); // конструктор

~AnyClass(); // деструктор

voidShow();

};

AnyClass :: AnyClass(int x, int y)

{

cout<<"В конструкторе \n";

a=x;

b=y;

}

AnyClass ::~AnyClass()

{

cout<<"В деструкторе \n";

}

voidAnyClass::Show()

{

cout<<a<<"\t"<<b<<"\n";

}

intmain()

{

AnyClass ob(3,7); // создание объекта

ob.Show();

}

Пример 5.

Файл Dot.h

#include<iostream.h>// содержит функции ввода-вывода

#include<windows.h>// содержит функцию CharToOem

#include<math.h>// содержит математические функции

class Dot // класс точки

{

char name; // имя точки

double x, y; // координаты точки

public:

// конструктор с параметрами

Dot(char Name, double X, double Y){name=Name; x=X; y=Y;}

void Print(); // выводит на экран имя и координаты текущей точки

};

Файл Dot.cpp

#include "Dot.h" // подключение файла класса Dot

void Dot :: Print()

{

char S[]="Координаты точки"; // объявляет и инициализирует строку заголовка

CharToOem(S, S); // преобразует символы строки в кириллицу

cout<<S<<name<<":"; // выводит на экран заголовок и имя точки

cout<<"\tx="<<x<<"\ty="<<y<<'\n';

}

Файл Main.cpp

#include "Dot.h" // подключение файла класса Dot

int main()

{

Dot A('A', 3, 4); // создание объекта

A.Print(); // вызов функции-члена класса

}

Здесь значение, переданное в конструктор при объявлении объекта A, используется для инициализации закрытых членов name, x и y этого объекта.

Фактически синтаксис передачи аргумента конструктору с параметрами является сокращенной формой записи следующего выражения:

Dot A=Dot('A', 3, 4);

Однако практически всегда используется сокращенная форма синтаксиса, приведенная в примере.

В отличие от конструктора, деструктор не может иметь параметров. Понятно, почему это сделано: незачем передавать аргументы удаляемому объекту.

Правила для конструкторов

Правила для конструкторов:

o конструктор класса вызывается всякий раз, когда создается объект его класса;

o конструктор обычно инициализирует данные-члены класса и резервирует память для динамических членов;

o конструктор имеет то же имя, что и класс, членом которого он является;

o для конструктора не указывается тип возвращаемого значения;

o конструктор не может возвращать значение;

o конструктор не наследуется;

o класс может иметь несколько перегруженных конструкторов;

o конструктор не может быть объявлен с модификатором const, volatile, static или virtual;

o если в классе не определен конструктор, компилятор генерирует конструктор по умолчанию, не имеющий параметров.

Правила для деструкторов

o деструктор вызывается при удалении объекта;

o деструктор обычно выполняет работу по освобождению памяти, занятой объектом;

o деструктор имеет то же имя, что и класс, которому он принадлежит, с предшествующим символом ~;

o деструктор не может иметь параметров;

o деструктор не может возвращать значение;

o деструктор не наследуется;

o класс не может иметь более одного деструктора;

o деструктор не может быть объявлен с модификатором const, volatile или static;

o если в классе не определен деструктор, компилятор генерирует деструктор по умолчанию.

Подчеркнем еще раз: конструкторы и деструкторы в C++ вызываются автоматически, что гарантирует правильное создание и удаление объектов класса.

Список инициализации элементов

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

ConstrName(par1, par2): mem1(par1) ,mem2(par2), …{…}

В следующем примере для инициализации класса используется список инициализации элементов.

Пример 6.

classAnyClass

{

inta, b;

public:

AnyClass(int x, int y): a(x), b(y) {};

};

Пример 7.

class Dot

{

char name; // имя точки

double x, y; // координаты точки

public:

// конструктор со списком инициализации

Dot(char Name):name(Name), x(0), y(0) {}

};

Хотя выполнение инициализации в теле конструктора или с помощью списка инициализации – дело вкуса программиста, список инициализации является единственным методом инициализации данных-констант и ссылок. Если членом класса является объект, конструктор которого требует задания значений одного или нескольких параметров, то единственно возможным способом его инициализации также является список инициализации.

C++ определяет два специальных вида конструкторов: конструктор по умолчанию, о котором мы упоминали выше, и конструктор копирования.

Конструкторы по умолчанию

Конструктор по умолчанию не имеет параметров (или все его параметры должны иметь значения по умолчанию) и вызывается при создании объекта, которому не заданы аргументы. Следует избегать двусмысленности при вызове конструкторов. В приведенном ниже примере два конструктора по умолчанию являются двусмысленными:

Пример 7.

class Т

{

public:

Т(); // конструктор по умолчанию

Т(int i=0); // конструктор с одним необязательным параметром

// может быть использован как конструктор по умолчанию

};

void main()

{

Т оb1(10); // Использует конструктор T::T(int)

Т оb2; // Не верно; неоднозначность вызова Т::Т() или Т::Т(int=0)

}

В данном случае, чтобы устранить неоднозначность, достаточно удалить из объявления класса конструктор по умолчанию.

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

Конструктор копирования создает объект класса, копируя при этом данные из уже существующего объекта данного класса. В связи с этим он имеет в качестве единственного параметра константную ссылку на объект класса (const Т&) или просто ссылку на объект класса (Т&). Использование первого предпочтительнее, так как последний не позволяет копировать константные объекты.

Пример 8.

class Coord;

{

double x, y; // координаты точки

public:

// конструктор копирования

Coord (const Coord& src);

};

Coord :: Coord (const Coord& src)

{

x=src.x;

y=src.y;

}

void main()

{

Coord ob1(2,9);

Coord ob2=ob1;

Coord ob2(ob1);

}

Ссылка передается всякий раз, когда новый объект инициализируется значениями существующего объекта. Если вы не предусмотрели конструктор копирования, компилятор генерирует конструктор копирования по умолчанию. В C++ различают поверхностное и глубинное копирование данных.

При поверхностном копировании происходит передача только адреса от одной переменной к другой, в результате чего оба объекта указывают на одни и те же ячейки памяти. Конструктор копирования по умолчанию, созданный компилятором, создает буквальную (или побитную) копию объекта, то есть осуществляет поверхностное копирование. Полученная копия объекта, скорее всего, будет непригодной, если она содержит указатели или ссылки.

В случае глубинного копирования происходит действительное копирование значений всех переменных из одной области памяти в другую. Если эти указатели или ссылки ссылаются на динамически распределенные объекты или на объекты, встроенные в копируемый объект, они будут недоступны в созданной копии объекта. Поэтому для классов, содержащих указатели и ссылки, следует включать в определение класса конструктор копирования, который будет осуществлять глубинное копирование, не полагаясь на созданный компилятором конструктор копирования по умолчанию. В этом конструкторе, как правило, выполняется копирование динамических структур данных, на которые указывают или на которые ссылаются члены класса. Класс должен содержать конструктор копирования, если он перегружает оператор присваивания.

Если в классе не определен конструктор, компилятор пытается сгенерировать собственный конструктор по умолчанию и, если нужно, собственный конструктор копирования. Эти сгенерированные компилятором конструкторы рассматриваются как открытые функции-члены. Подчеркнем, что компилятор генерирует эти конструкторы, если в классе не определен никакой другой конструктор.

Сгенерированный компилятором конструктор по умолчанию создает объект и вызывает конструкторы по умолчанию для базовый классов и членов-данных, но не предпринимает никаких других действий. Конструкторы базового класса и членов-данных вызываются, только если они существуют, доступны и являются недвусмысленными. Когда конструируется объект производного класса, конструирование начинается с объектов его базовых классов и продвигается вниз по иерархии классов, пока не будет создан заданный объект.

Сгенерированный компилятором конструктор копирования создает новый объект и выполняет почленное копирование содержимого исходного объекта.

Пример 9.

class Dot // класс точки

{

char name; // имя точки

double x, y; // координаты точки

public: // открытые члены класса

// конструкторы с параметрами

Dot(char Name, double X, double Y)

{

name=Name;

x=X;

y=Y;

}

Dot(char Name):name(Name), x(0), y(0){}

Dot(char Name, const Dot& A)

{

name=Name;

x=A.x;

y=A.y;

}

// конструктор копирования

Dot(const Dot& A)

{

name=(char)226; // переменная принимает значение буквой 'т'

x=A.x;

y=A.y;

}

void Print(); // выводит на экран имя и координаты текущей точки

};

void main()

{

Dot A('A', 3, 4); // вызов конструктора Dot(char Name, double X, double Y)

Dot B('B'); // вызов конструктора Dot (char Name)

Dot C(A); // вызов конструктора копирования Dot (const Dot& A)

C.Print(); // выводит на экран: Координаты точки т: x=3 y=4

Dot D=B; // вызов конструктора копирования Dot (const Dot& A)

D.Print(); // выводит на экран: Координаты точки т: x=0 y=0

Dot E('E', A); // вызов конструктора Dot (char Name ,const Dot& A)

E.Print(); // выводит на экран: Координаты точки E: x=3 y=4

}

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

В приведённом примере точки C и D объявлены с помощью конструктора копирования объявленного нами. В этом конструкторе имя точки инициализируется фиксированным значением – русской буквой 'т'.

Для решения этой задачи в приведённом примере объявлен конструктор:

Dot (char Name, const Dot& A) {name=Name; x=A.x ; y=A.y;}

Этот конструктор в качестве параметров принимает имя и точку, координаты которой используются для инициализации объявляемой точки.

состояние объекта.


Сейчас читают про: