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

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

С помощью виртуальных функций можно преодолеть трудности, возникающие

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

которые могут переопределяться в любом производном классе. Транслятор

и загрузчик обеспечат правильное соответствие между объектами и

применяемыми к ним функциями:

class employee {

char* name;

short department;

// ...

employee* next;

static employee* list;

public:

employee(char* n, int d);

// ...

static void print_list();

virtual void print() const;

};

Служебное слово virtual (виртуальная) показывает, что функция print()

может иметь разные версии в разных производных классах, а выбор нужной

версии при вызове print() - это задача транслятора.

Тип функции указывается в базовом классе и не может быть

переопределен в производном классе. Определение виртуальной функции

должно даваться для того класса, в котором она была впервые

описана (если только она не является чисто виртуальной функцией,

см. $$6.3). Например:

void employee::print() const

{

cout << name << '\t' << department << '\n';

// ...

}

Мы видим, что виртуальную функцию можно использовать, даже если

нет производных классов от ее класса. В производном же классе

не обязательно переопределять виртуальную функцию, если она там

не нужна. При построении производного класса надо определять

только те функции, которые в нем действительно нужны:

class manager : public employee {

employee* group;

short level;

// ...

public:

manager(char* n, int d);

// ...

void print() const;

};

Место функции print_employee() заняли функции-члены print(), и она

стала не нужна. Список служащих строит конструктор employee ($$6.2.2).

Напечатать его можно так:

void employee::print_list()

{

for ( employee* p = list; p; p=p->next) p->print();

}

Данные о каждом служащем будут печататься в соответствии с типом

записи о нем. Поэтому программа

int main()

{

employee e("J.Brown",1234);

manager m("J.Smith",2,1234);

employee::print_list();

}

напечатает

J.Smith 1234

level 2

J.Brown 1234

Обратите внимание, что функция печати будет работать даже в том случае,

если функция employee_list() была написана и оттранслирована еще до того,

как был задуман конкретный производный класс manager! Очевидно, что для

правильной работы виртуальной функции нужно в каждом объекте класса

employee хранить некоторую служебную информацию о типе. Как правило,

реализации в качестве такой информации используют просто указатель.

Этот указатель хранится только для объектов класса с виртуальными

функциями, но не для объектов всех классов, и даже для не для всех

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

для классов, в которых описаны виртуальные функции. Заметим, что

при использовании поля типа, для него все равно нужна дополнительная

память.

Если в вызове функции явно указана операция разрешения

области видимости ::, например, в вызове manager::print(),

то механизм вызова виртуальной функции не действует. Иначе подобный

вызов привел бы к бесконечной рекурсии. Уточнение имени функции

дает еще один положительный эффект: если виртуальная функция

является подстановкой (в этом нет ничего необычного), то в вызове

с операцией :: происходит подстановка тела функции. Это

эффективный способ вызова, который можно применять

в важных случаях, когда одна виртуальная функция

обращается к другой с одним и тем же объектом. Пример такого

случая - вызов функции manager::print(). Поскольку тип объекта

явно задается в самом вызове manager::print(), нет нужды определять

его в динамике для функции employee::print(), которая и будет

вызываться.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]