Г. Шилтд - Самоучитель C++ (PDF) (1114887), страница 45
Текст из файла (страница 45)
Другими словами, тип объекта, на который ссылается указатель, и определяет ту версию виртуальной функции, которая будетвыполняться. Поэтому, если два или более различных класса являютсяпроизводными от базового, содержащего виртуальную функцию, то, еслиуказатель базового класса ссылается на разные объекты этих производныхГлава 10, Виртуальные функции307классов, выполняются различные версии виртуальной функции. Этотпроцесс является реализацией принципа динамического полиморфизма.Фактически, о классе, содержащем виртуальную функцию, говорят как ополиморфном классе (polymorphic class).1. Рассмотрим короткий пример использования виртуальной функции:// Простой пример использования виртуальной функции#include <iostream>using namespace std;class base {public :int i;base {int x} { i = x; }virtual void func(){cout « "Выполнение функции func() базового класса: ";cout « i « '\n';class derivedl : public base (public :derivedKint x) : base(x) ( }void f unc ( ){cout « "Выполнение функции func() класса derivedl:cout « i * i « '\n' ;}ь-class derived2: public base {public:derived2 (int x) : base(x) { }void f unc { }{cout « "Выполнение функции f unc ( ) класса derived2 : "cout « i + i « '\n' ;int main(}{base *p;308__Самоучитель C++base ob (10) ;derivedl d_obl(10);derived2 d_ob2(10);p = sob;p->func ( ) ; // функция f unc ( ) класса basep = &d_obl;p->func(); // функция func{) производного класса derivedlp = &d_ob2;p->func(); // функция f unc ( ) производного класса derived2return 0;После выполнения программы на экране появится следующее:Выполнение функции func ( ) базового класса: 10Выполнение функции f unc ( ) класса derivedl: 100Выполнение функции func() класса derived2: 20Переопределение виртуальной функции внутри производного класса можетпоказаться похожим на перегрузку функций.
Однако эти два процесса совершенно различны. Во-первых, перегружаемая функция должна отличатьсятипом и/или числом параметров, а переопределяемая виртуальная функциядолжна иметь точно такой же тип параметров, то же их число, и такой жетип возвращаемого значения. (На самом деле, если при переопределениивиртуальной функции вы изменяете число или тип параметров, она простостановится перегружаемой функцией и ее виртуальная природа теряется.)Далее, виртуальная функция должна быть членом класса. Это не относится кперегружаемым функциям. Кроме этого, если деструкторы могут быть виртуальными, то конструкторы нет.
Чтобы подчеркнуть разницу между перегружаемыми функциями и переопределяемыми виртуальными функциями, дляописания переопределения виртуальной функции используется термин подмена (overriding).В рассмотренном примере создается три класса. В классе base определяетсявиртуальная функция func(). Затем этот класс наследуется двумя производными классами: derivedl и derived!. Каждый из этих классов переопределяетфункцию func() по-своему.
Внутри функции main() указатель базового классар поочередно ссылается на объекты типа base, derivedl и derived!. Первымуказателю р присваивается адрес объекта ob (объекта типа base). При вызовефункции func () через указатель р используется ее версия из класса base. Следующим указателю р присваивается адрес объекта d_obl и функция func()вызывается снова. Поскольку версия вызываемой виртуальной функции определяется типом объекта, на который ссылается указатель, то вызывается таверсия функции, которая переопределяется в классе derivedl. Наконец, указателю р присваивается адрес объекта d_ob2, и снова вызывается функцияfunc().
При этом выполняется та версия функции func(), которая определенавнутри класса derived!.Главами.Виртуальныефункции_309Ключевым для понимания предьщущего примера является тот факт, что, вопервых, тип адресуемого через указатель объекта определяет вызов той илииной версии подменяемой виртуальной функции, во-вторых, выбор конкретной версии происходит уже в процессе выполнения программы.2. Виртуальные функции имеют иерархический порядок наследования. Крометого, если виртуальная функция не подменяется в производном классе, тоиспользуется версия функции, определенная в базовом классе. Например,ниже приведена слегка измененная версия предыдущей программы:// Иерархический порядок виртуальных функций#include <iostream>using namespace std;class base {public:• *.
i;•intbase (int x) { i = x; }virtual void £unc{){cout « "Выполнение функции f u n c f ) базового класса: ";cout « i « ' \ n ' ;}} /*-class derivedl: public base {public:derivedl (int x) : base(x) { }void f unc ( ){cout « "Выполнение функции func() класса derivedl: ";cout « i * i « *\n';class derived2: public base {public :derived2(int x) : base(x) { }// в классе derived2 функция f unc ( ) не подменяется}/int rnainO{base *p;base ob(10) ;derivedl d_obl(10) ;derived2 d_ob2(10);p = sob;p->func(); // функция func ( ) базового класса310Самоучитель C++р = &d_obl;p->func(); // функция func{) производного класса derivedlр = &d_ob2;p->func{); // функция func() базового классаreturn 0;После выполнения программы на экран выводится следующее:Выполнение функции func() базового класса: 10Выполнение функции f unc ( ) класса derivedl: 100Выполнение функции f unc { ) базового класса: 10В этой программе в классе derivedl функция func() не подменяется.
Когдауказателю р присваивается адрес объекта d_ob2 и вызывается функция func(),используется версия функции из класса base, поскольку она следующая в иерархии классов. Обычно, если виртуальная функция не переопределена впроизводном классе, используется ее версия из базового класса.3. В следующем примере показано, как случайные события во время работыпрограммы влияют на вызываемую версию виртуальной функции. Программавыбирает между объектами d_obl и d_ob2 на основе значений, возвращаемыхстандартным генератором случайных чисел rand(). Запомните, выбор конкретной версии функции func() происходит во время работы программы.(Действительно, при компиляции этот выбор сделать невозможно, посколькуон основан на значениях, которые можно получить только во время работыпрограммы.)/* В этом примере показана работа виртуальной функции при наличиислучайных событий во время выполнения программы.V# include <iostream>^include <cstdlib>using namespace std;class base {public:int i ;base (int x} { i = x; }virtual void func(){cout « "Выполнение функции func() базового класса: ";cout « i « '\n' ;class derivedl: public base {public:derivedl (int x) : base(x) { )Глава 10.
Виртуальные функции311void fume ( )cout « "Выполнение функции func() класса derivedl: ";cout « i * i « '\n';class derived2: public base {public:derived2(int x) : b a s e ( x ) { }void func ( )cout « "Вьшолнение функции f u n c ( ) класса derived2: ";cout « i + i « ' \ n ' ;int m a i n ( )base *p;derivedl d_obl(10);derived2 d_ob2(10);int i, j;for(i=0;j = rand();if ( ( j % 2 ) > p = &d_obl; ////else p = &d_ob2;////p->func {) ;//если число нечетноеиспользовать объект d_oblесли число четноеиспользовать объект d_ob2вызов подходящей версии функцииreturn 0;4. Теперь более реальный пример использования виртуальной функции.
В этойпрограмме создается исходный базовый класс area, в котором сохраняютсядве размерности фигуры. В нем также объявляется виртуальная функцияgetareaQ, которая, при ее подмене в производном классе, возвращает площадь фигуры, вид которой задается в производном классе. В этом случае определение функции getarea() внутри базового класса задает интерфейс.Конкретная реализация остается тем классам, которые наследуют класс area.В этом примере рассчитывается площадь треугольника и прямоугольника.// Использование виртуальной функции для определения интерфейса^include <iostream>using namespace std;312Самоучитель C++class area {double diml, dim2; // размеры фигурыpublic:void setarea(double dl, double d2)diml = dl;dim2 = d2;void getdim(double sdl, double &d2)dl = diml;d2 = dim2;virtual double getareaOcout « "Вам необходимо подменить эту функцикЛп";return 0.0;Ьclass rectangle: public area {public:double getareaOdouble dl, d2;getdim{dl, d2);return dl * d2;}};class triangle: public area {public:double getarea()double dl, d2;getdim(dl, d2);return 0.5 * dl * d2;int mainf)(area *p;rectangle r;triangle t;r.setarea{3.3, 4.5)t.setarea(4.0, 5.0)Глава 10.
Виртуальные функции313Р — &г;cout « "Площадь прямоугольника: " « p->getarea() « '\п';р = fit;cout « "Площадь треугольника: " « p->getarea() « '\п';return 0;Обратите внимание, что определение getarea() внутри класса area являетсятолько "заглушкой" и, в действительности, не выполняет никаких действий.Поскольку класс area не связан с фигурой конкретного типа, то нет значимого определения, которое можно дать функции getarea() внутри класса area.При этом, для того чтобы нести полезную нагрузку, функция getarea() должна быть переопределена в производном классе. В следующем разделе вы узнаете об этом подробнее.Напишите программу создания базового класса num. В этом классе должнохраниться целое и определяться виртуальная функция shownum(). Создайтедва производных класса outhex и outoct, которые наследуют класс num.Функция shownum() должна быть переопределена в производных классах так,чтобы осуществлять вывод на экран значений, в шестнадцатеричной и восьмеричной системах счисления соответственно.2.
Напишите программу, в которой базовый класс dist используется для хранения в переменной типа double расстояния между двумя точками. В классе distсоздайте виртуальную функцию trav_time(), которая выводит на экран время,необходимое для прохождения этого расстояния с учетом того, что расстояние задано в милях, а скорость равна 60 миль в час. В производном классеmetric переопределите функцию trav_time() так, чтобы она выводила на экран время, необходимое для прохождения этого расстояния, считая теперь,что расстояние задано в километрах, а скорость равна 100 километров в час.10.3. Дополнительные сведенияо виртуальных функцияхКак показано в примере 4 предыдущего раздела, иногда, когда виртуальнаяфункция объявляется в базовом классе, она не выполняет никаких значимых действий.
Это вполне обычная ситуация, поскольку часто в базовомклассе законченный тип данных не определяется. Вместо этого в нем простосодержится базовый набор функций-членов и переменных, для которых впроизводном классе определяется все недостающее. Когда в виртуальной314Самоучитель C++функции базового класса отсутствует значимое действие, в любом классе,производном от этого базового, такая функция обязательно должна быть переопределена.