Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Кетков.doc
Скачиваний:
17
Добавлен:
27.09.2019
Размер:
2.22 Mб
Скачать

16.1.3. Динамическое создание и удаление объектов

Объявление объектов с использованием конструкторов создает данные, которые существуют до выхода из блока, в котором они появились. Однако иногда объекты могут потребоваться на более короткое время. Такие объекты можно создавать и уничтожать во время работы программы с помощью операторов new и delete:

class A {...}; //объявление класса

..............

A *ps=new A; //объявление указателя и создание объекта типа A

A* *pa=new A[20]; //объявление указателя и создание массива объектов

...............

delete ps; //удаление объекта по указателю ps

delete [] pa; //удаление массива объектов по указателю pa

Фактически, выполнение оператора new эквивалентно вызову конструктора класса, а обращение к оператору delete на автомате означает вызов деструктора. Создание одиночных объектов может быть совмещено с инициализацией объекта, если в классе предусмотрен соответствующий конструктор:

A *ptr1=new A(5);//создание объекта и вызов конструктора инициализации

Массив создаваемых объектов проинициализировать таким же образом нельзя.

В ранних версиях C++ для создания и уничтожения динамических объектов использовали обращения к функциям malloc (запрос памяти) и free (освобождение памяти). Неудобство применения этих функций по сравнению с операторами new/delete заключается в том, что для запроса памяти нужно знать количество байт, занимаемых объектом в оперативной памяти. Конечно, это не так уж и сложно – существует функция sizeof, с помощью которой длину объекта можно определить. Второе неудобство заключается в том, что функция malloc выдает указатель типа void* и его еще надо преобразовать к типу указателя на объект класса.

Довольно распространенная ситуация, которая может оказаться потенциальным источником ошибок, возникает в процессе создания и удаления динамических объектов. Она заключается в том, что после уничтожения объекта, связанного, например, с указателем ps, этот указатель не чистится. Если после удаления объекта сделать попытку что-то прочитать или записать по этому указателю, то поведение программы предсказать трудно. Поэтому достаточно разумным правилом является засылка нуля в указатель разрушаемого объекта:

delete ps;

ps=NULL; //или ps=0;

16.1.4. Виртуальные функции

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

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

Рассмотрим пример, в котором базовый класс B содержит защищенное поле n и отображает его содержимое на экране. Производный класс D1 отображает квадрат доставшегося по наследству поля. Еще один класс D2, порожденный тем же родителем B, отображает куб своего наследства.

#include <iostream.h>

#include <conio.h>

class B {

public:

B(int k):n(k){} //конструктор инициализации

virtual void show(){cout<<n<<endl;} //виртуальная функция

protected:

int n;

};

class D1: public B {

public:

D1(int k):B(k){} // конструктор инициализации

virtual void show(){cout<<n*n<<endl;}

};

class D2: public B {

public:

D2(int k):B(k){} // конструктор инициализации

virtual void show(){cout<<n*n*n<<endl;}

};

void main()

{ B bb(2),*ptr;

D1 dd1(2);

D2 dd2(2);

ptr=&bb;

ptr->show();

ptr=&dd1;

ptr->show();

ptr=&dd2;

ptr->show();

getch();

}

//=== Результат работы ===

2 //результат работы функции B::show

4 //результат работы функции D1::show

8 //результат работы функции D2::show

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

B *bptr;

D1 dd1(2);

bptr=&dd1; //вниз по иерархии классов без преобразования

D1 *dptr;

dptr=(D1 *)bptr; //вверх по иерархии с преобразованием типа