    В данном случае употреблять ключевое слово virtual не обязательно, 
поскольку С находится в конце линии наследования. Конечно, если еще один класс 
D будет выведен из класса С и в D потребуется заместить функцию vf(), то в С эта 
функция должна быть определена с ключевым словом virtual. Будьте готовы сейчас 
последует самое важное. Вернемся к указателю р типа А*, который ссылается на 
объект cObject типа С*. Внимательно присмотритесь к оператору, вызывающему 
виртуальную функцию vf() для объекта, на который ссылается р: 

    p->vf();	 

    Указатель р может хранить адрес объекта типа А, В или С. Следовательно, во 
время выполнения этот оператор вызывает виртуальную функцию класса объекта, на 
который ссылается р. Если р ссылается на объе типа А, оператор вызовет функцию 
vf(), принадлежащую классу А. Если р ссылается на объект типа В, оператор 
вызовет функцию vf(), принадлежащую классу В. Один и тот же оператор вызывает 
функцию класса адресуемого объекта — действие, определяемое во время выполнения. 

    Виртуальные функции в реальной жизни 

    В предыдущем разделе описывалась теория виртуальных функций в общих 
чертах. Еще один классически пример поможет уяснить их практическое значение в 
реальном мире программирования. 
    Вообразите, что вы пишете графическую программу и хотите разработать 
различные классы для таких фигур, как круги, квадраты и линии. Поскольку все 
эти классы взаимосвязаны (все они — фигуры), сначала и создаете базовый класс 
Shape следующим образом: 

    class Shape { 
    public:	
        ..........  // ... разнообразные члены 
        virtual void DrawO; 
    }: 
 
    Вы еще не знаете, какие типы фигур вам потребуются, кроме того, вы 
захотите позже добавить новые фи гуры. Конечно, вам известно, что необходимо 
где-то хранить коллекцию объектов Shape, вероятно, в массив указателей 
(предположим, он объявлен глобальным, и все указатели инициализированы нулевым значением): 

    Shape *picture[100];	// Массив указателей на объекты Shape 

    В соответствии с объявлением, массив picture может хранить адреса 100 
объектов Shape. Каждый элемент массива — указатель типа Shape*, который, как 
следует из предыдущего раздела и как вам уже известно, может ссылаться на 
объект типа Shape или любого производного от него класса. Поскольку в классе 
Shape определена виртуальная функция Draw(), вы можете запрограммировать цикл, 
вызывающий DrawO для указателей из массива picture: 

    int i = 0; 
    while (i < 100 && _picture[i] != 0)  { 
       // Вызвать виртуальную функцию Draw()             
       picture[i]->Draw(); 
       i++; 
    }

    Вы только что написали код, рисующий картинку до того, как у вас появилась 
хотя бы одна существующая фигура для прорисовки! Если и это не произвело на 
вас впечатления, имейте в виду, что вы можете скомпилировать этот код и 
сохранить его в библиотеке или .OBJ-файле. Позже вы (или другой программист) 
можете вывести новые классы из Shape, сохранить объекты этих классов в массиве 
picture, и цикл while будет вызывать виртуальные функции Draw() этих объектов 
для отрисовки соответствующих изображений. 
    Особо заметьте, что этот код не требует от вас точного задания типов 
данных объектов, на которые ссылаются указатели picture, требуется только 
чтобы эти объекты были производными от Shape. Объекты определяют во время 
выполнения, какую функцию Draw() следует вызвать. Предположим, вы хотите 
вывести классы Circle (круг) и Line (линия) из класса Shape: 
    
    class Circle:   public Shape { 
    public: 
       virtual void Draw(); 
    }; 
    
    class Line: public Shape { 
    public: 
       virtual void Draw(); 
    }; 

    Вам также необходимо запрограммировать замещающие функции Draw в классах 
Circle и Line для отрисовки соответствующих фигур. При выполнении программы 
рисующий цикл вызывает необходимые виртуальные функции, исходя из типа 
адресуемого объекта. Например, вы можете добавить в массив picture фигуры 
подобные следующим: 


    352 Часть III. Программирование на ANSI C++
