Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
КРАТКИЙ ОБЗОР С.doc
Скачиваний:
1
Добавлен:
26.10.2018
Размер:
2.11 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(), которая и будет       вызываться.