Программа 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;

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

Абстрактный класс может быть использован только как базовый класс для производных классов. Нельзя создавать объекты абстрактных классов. Механизм абстрактных классов разработан для представления общих понятий, которые в дальнейшем предполагается конкретизировать. Эти общие понятия, обычно, невозможно использовать непосредственно. Например, можно говорить об общем понятии «фигура» на плоскости. Это понятие включает свойства, присущие любой конкретной фигуре: треугольнику, прямоугольнику, окружности. Например, у любой фигуры есть свойство цвет. Но изобразить можно всегда только конкретную фигуру, а не фигуру вообще.



Наши рекомендации