Программа 60. Невиртуальные функции
// Файл NoVirtF.cpp
#include <iostream.h>
struct base{ // Базовый класс base
void fun(int i) // Функция базового класса
{cout << "\n base::i= " << i;}
};
struct derive: public base{ // Производный класс derive
void fun(int i) // Функция производного класса
{cout << "\n derive::i= " << i;}
};
#include <conio.h>
void main()
{
base B, *pb = &B; // Объект base и указатель на base
derive D, *pd = &D; // Объект derive и указатель на derive
base* pbd = &D; // Указатель на base инициализируется
// адресом объекта derive
pb->fun(1); // Печатает base::i=1
pd->fun(5); // Печатает derive::i=5
pbd->fun(4); // Печатает base::i=4;
getch();
}
Указателю на базовый класс можно присвоить адрес объекта производного класса, т.к. в производном классе есть все члены, которые есть в базовом. Обратное невозможно, т.е. указателю на производный класс нельзя присвоить адрес объекта базового класса, т.к. в базовом классе, вообще говоря, нет всех компонент, которые есть в производном. Например:
base B; // Объект базового класса
derive *pd; // Указатель на производный класс
pd = &B; // Ошибка! Нельзя указателю на производный класс
// присвоить адрес объекта базового класса
Так как pbd есть указатель на базовый класс, при обращении
pbd -> fun(4);
вызывается функция базового класса base::fun(), а не функция производного класса derive::fun(), несмотря на то, что указатель pbd имеет значение адреса объекта производного класса.
Выбор нужной функции устанавливается при компиляции программы в соответствии с типом указателя, а не с его значением, которое может быть разным в разные моменты работы программы. Такой режим называется ранним или статическим связыванием.
Позднее или динамическое связывание обеспечивают виртуальные функции. Рассмотрим пример.
Программа 61. Виртуальные функции
// Файл VirtualF.cpp
#include <iostream.h>
struct base{ // Базовый класс base
virtual void vfun(int i) // Виртуальная функция базового класса
{cout << "\n base::i= " << i;}
};
struct derive1: public base{ // Производный класс derive1
void vfun(int i) // Переопределение виртуальной функции
{cout << "\n derive1::i= " << i;}
};
struct derive2: public base{ // Еще один производный класс derive2
void vfun(int i) // Переопределение виртуальной Функции
{cout << "\n derive2::i= " << i;}
};
#include <conio.h>
void main()
{
base B, *pb = &B; // Объект base и указатель на base
derive1 D1, *pd1 = &D1; // Объект derive1 и указатель на derive1
derive2 D2, *pd2 = &D2; // Объект derive2 и указатель на derive2
pb->vfun(1); // Печатает base::i=1
pd1->vfun(2); // Печатает derive1::i=2
pd2->vfun(3); // Печатает derive2::i=3
pb = &D1; // Изменяем значение указателя на базовый класс
pb->vfun(4); // Печатает derive1::i=4
pb = &D2; // Еще раз изменяем указатель на базовый класс
pb->vfun(5); // Печатает derive2::i=5
getch();
}
Интерпретация каждого вызова виртуальной функции через указатель на базовый класс зависит от значения указателя. В табл.28 указаны функции, вызываемые в зависимости от значения указателя.
Таблица 28. Вызовы виртуальной функции
Значение указателя pb | Функция, вызываемая инструкцией pb->vfun |
&B &D1 &D2 | base::vfun() derive1::vfun() derive2::vfun() |
Так как при компиляции программы невозможно предвидеть, какое значение примет указатель на базовый класс при работе программы, вопрос о выборе виртуальной функции решается на этапе выполнения программы и определяется значением указателя.
При вызове через указатель невиртуальной функции интерпретация определяется только типом, а не значением указателя.
Виртуальными могут быть только функции-члены класса.
В производном классе при объявлении виртуальной функции слово virtual можно не использовать, но лучше использовать, чтобы сделать программу более понятной.
Абстрактные классы
Абстрактным называется класс, в котором есть хотя бы одна чистая виртуальная функция. Чистой виртуальной функцией называется функция-член, которая имеет следующее определение:
virtual <ТИП><ИМЯ_ФУНКЦ>(<СПИСОК_ФОРМАЛЬН_ПАРАМ>) = 0;
Конструкция = 0 называется чистый спецификатор.
Следующая функция является чистой виртуальной:
virtual void fpure(void) = 0;
Чистая виртуальная функция ничего не делает и недоступна для вызовов. Она служит основой для подменяющих ее функций в производных классах.
Абстрактный класс может быть использован только как базовый класс для производных классов. Нельзя создавать объекты абстрактных классов. Механизм абстрактных классов разработан для представления общих понятий, которые в дальнейшем предполагается конкретизировать. Эти общие понятия, обычно, невозможно использовать непосредственно. Например, можно говорить об общем понятии «фигура» на плоскости. Это понятие включает свойства, присущие любой конкретной фигуре: треугольнику, прямоугольнику, окружности. Например, у любой фигуры есть свойство цвет. Но изобразить можно всегда только конкретную фигуру, а не фигуру вообще.