Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии 


Глава 15: Виртуальные функции и полиморфизм




Одной из трех основных граней объектно-ориентированного программирования является полиморфизм. Применительно к C++ полиморфизм представляет собой термин, используемый для описания процесса, в котором различные реализации функции могут быть доступны посредством одного и того же имени. По этой причине полиморфизм иногда характеризуется фразой "один интерфейс, много методов". Это означает, что ко всем функциям-членам общего класса можно получить доступ одним и тем же способом, несмотря на возможное различие в конкретных действиях, связанных с каждой отдельной операцией.

В C++ полиморфизм поддерживается как во время выполнения, так в период компиляции программы. Перегрузка операторов и функций — это примеры полиморфизма, относящегося ко времени компиляции. Но, несмотря на могущество механизма перегрузки операторов и функций, он не в состоянии решить все задачи, которые возникают в реальных приложениях объектно-ориентированного языка программирования. Поэтому в C++ также реализован полиморфизм периода выполнения на основе использования производных классов и виртуальных функций, которые и составляют основные темы этой главы.

Начнем же мы эту главу с краткого описания указателей на производные типы, поскольку именно они обеспечивают поддержку динамического полиморфизма.

Указатели на производные типы

Указатель на базовый класс может ссылаться на любой объект, выведенный из этого базового класса.

Фундаментом для динамического полиморфизма служит указатель на базовый класс. Указатели на базовые и производные классы связаны такими отношениями, которые не свойственны указателям других типов. Как было отмечено выше в этой книге, указатель одного типа, как правило, не может указывать на объект другого типа. Однако указатели на базовые классы и объекты производных классов — исключения из этого правила. В C++ указатель на базовый класс также можно использовать для ссылки на объект любого класса, выведенного из базового. Например, предположим, что у нас есть базовый класс B_class и класс D_class, который выведен из класса B_class. В C++ любой указатель, объявленный как указатель на класс B_class, может быть также указателем на класс D_class. Следовательно, после этих объявлений

B_class *р; // указатель на объект типа B_class

B_class B_ob; // объект типа B_class

D_class D_ob; // объект типа D_class

обе следующие инструкции абсолютно законны:

р = &В_ob; // р указывает на объект типа B_class

р = &D_ob; /* р указывает на объект типа D_class, который является объектом, выведенным из класса B_class. */

В этом примере указатель р можно использовать для доступа ко всем элементам объекта D_ob, выведенным из объекта В_ob. Однако к элементам, которые составляют специфическую "надстройку" (над базой, т.е. над базовым классом B_class) объекта D_ob, доступ с помощью указателя р получить нельзя.

В качестве более конкретного примера рассмотрим короткую программу, которая определяет базовый класс B_class и производный класс D_class. В этой программе простая иерархия классов используется для хранения имен авторов и названий их книг.

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

#include <iostream>

#include <cstring>

using namespace std;

class B_class {

   char author[80];

  public:

   void put_author(char *s) { strcpy(author, s); }

   void show_author() { cout << author << ""; }

};

class D_class : public B_class {

  char title [80];

 public:

  void put_title(char *num) { strcpy(title, num);}

  void show_title() {

   cout << "Название: ";

   cout << title << "";

  }

};

Int main()

{

 B_class *p;

 B_class B_ob;

 D_class *dp;

 D_class D_ob;

 p = &B_ob; // адрес объекта базового класса

 // Доступ к классу B_class через указатель.

 p->put_author("Эмиль Золя");

 // Доступ к классу D_class через "базовый" указатель.

 р = &D_ob;

 p->put_author("Уильям Шекспир");

 // Покажем, что каждый автор относится к соответствующему объекту.

 B_ob.show_author();

 D_ob.show_author();

 cout << "";

 /* Поскольку функции put_title() и show_title() не являются частью базового класса, они недоступны через "базовый" указатель р, и поэтому к ним нужно обращаться либо непосредственно, либо, как показано здесь, через указатель на производный тип.

 */

 dp = &D_ob;

 dp->put_title("Буря");

 p->show_author(); // Здесь можно использовать либо указатель р, либо указатель dp.

 dp->show_title();

 return 0;

}

При выполнении эта программа отображает следующие результаты.

Эмиль Золя

Уильям Шекспир

Уильям Шекспир

Название: Буря

В этом примере указатель р определяется как указатель на класс B_class. Но он может также ссылаться на объект производного класса D_class, причем его можно использовать для доступа только к тем элементам производного класса, которые унаследованы от базового. Однако следует помнить, что через "базовый" указатель невозможно получить доступ к тем членам, которые специфичны для производного класса. Вот почему к функции show_title() обращение реализуется с помощью указателя dp, который является указателем на производный класс.

Если вам нужно с помощью указателя на базовый класс получить доступ к элементам, определенным производным классом, необходимо привести этот указатель к типу указателя на производный тип. Например, при выполнении этой строки кода действительно будет вызвана функция show_title() объекта D_ob:

((D_class *)р)->show_title();

Внешний набор круглых скобок используется для связи операции приведения типа с указателем р, а не с типом, возвращаемым функцией show_title(). Несмотря на то что в использовании такой операции формально нет ничего некорректного, этого по возможности следует избегать, поскольку подобные приемы попросту вносят в код программы путаницу. (На самом деле большинство С++-программистов считают такой стиль программирования неудачным.)

Кроме того, необходимо понимать, что хотя "базовый" указатель можно использовать для доступа к объектам любого производного типа, обратное утверждение неверно. Другими словами, используя указатель на производный класс, нельзя получить доступ к объекту базового типа.

Указатель инкрементируется и декрементируется относительно своего базового типа. Следовательно, если указатель на базовый класс используется для доступа к объекту производного типа, инкрементирование или декрементирование не заставит его ссылаться на следующий объект производного класса. Вместо этого он будет указывать на следующий объект базового класса. Таким образом, инкрементирование или декрементирование указателя на базовый класс следует расценивать как некорректную операцию, если этот указатель используется для ссылки на объект производного класса.

Тот факт, что указатель на базовый тип можно использовать для ссылки на любой объект, выведенный из базового, чрезвычайно важен и принципиален для C++. Как будет показано ниже, эта гибкость является ключевым моментом для способа реализации динамического полиморфизма в C++.

Ссылки на производные типы

Подобно указателям, ссылку на базовый класс также можно использовать для доступа к объекту производного типа. Эта возможность особенно часто применяется при передаче аргументов функциям. Параметр, который имеет тип ссылки на базовый класс, может принимать объекты базового класса, а также объекты любого другого типа, выведенного из него.

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

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

Виртуальная функция — это функция, которая объявляется в базовом классе с использованием ключевого слова virtual и переопределяется в одном или нескольких производных классах. Таким образом, каждый производный класс может иметь собственную версию виртуальной функции. Интересно рассмотреть ситуацию, когда виртуальная функция вызывается через указатель (или ссылку) на базовый класс. В этом случае C++ определяет, какую именно версию виртуальной функции необходимо вызвать, по типу объекта, адресуемого этим указателем. Причем следует иметь в виду, что это решение принимается во время выполнения программы. Следовательно, при указании на различные объекты будут вызываться и различные версии виртуальной функции. Другими словами, именно по типу адресуемого объекта (а не по типу самого указателя) определяется, какая версия виртуальной функции будет выполнена. Таким образом, если базовый класс содержит виртуальную функцию и если из этого базового класса выведено два (или больше) других класса, то при адресации различных типов объектов через указатель на базовый класс будут выполняться и различные версии виртуальной функции. Аналогичный механизм работает и при использовании ссылки на базовый класс.

Чтобы объявить функцию виртуальной, достаточно предварить ее объявление ключевым словом virtual.

Функция объявляется виртуальной в базовом классе с помощью ключевого слова virtual. При переопределении виртуальной функции в производном классе ключевое слово virtual повторять не нужно (хотя это не будет ошибкой).

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





Рекомендуемые страницы:


Читайте также:

Последнее изменение этой страницы: 2016-03-17; Просмотров: 1292; Нарушение авторского права страницы


lektsia.com 2007 - 2018 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.017 с.) Главная | Обратная связь