Void main(void)

{

base B, *bp = &В;

dir1 D1, *dp1 = &D1;

dir2 D2, *dp2 = &D2;

bp->vfun(l); // Печатает: base::i = 1

dpl->vfun(2); // Печатает: dirl::i = 2

dp2->vfun(3); // Печатает: dir2::i = 3

bp = &D1;

bp->vfun(4) ; // Печатает:dir1::i = 4

bp = &D2;

bp->vfun(5); // Печатает: dir2::i = 5

}

Результат выполнения программы:

base::i = 1

dirl::i = 2 dir2::i = 3 dirl::i = 4

dir2::i = 5

В примере надо обратить внимание на доступ к функциям vfun () че­рез указатель bр на базовый класс. Когда bр принимает значение адреса объекта класса base, то вызывается функция из базового класса. Затем bр последовательно присваиваются значения ссылок на объекты производ­ных классов &Dl, &D2, и выбор соответствующего экземпляра функции vfun () каждый раз определяется именно объектом. Таким образом, ин­терпретация каждого вызова виртуальной функции через указатель на ба­зовый класс зависит от значения этого указателя, т.е. от типа объекта, для которого выполняется вызов. В противоположность этому интерпретация вызова через указатель невиртуальной функции зависит только от типа указателя (это было показано в предыдущем примере с функцией fun()).

Виртуальными могут быть не любые функции, а только нестатические компонентные функции какого-либо класса. После того как функция опре­делена как виртуальная, ее повторное определение в производном классе (с тем же самым прототипом) создает в этом классе новую виртуальную функцию, причем спецификатор virtualможет не использоваться.

В производном классе нельзя определять функцию с тем же именем и с той же сигнатурой параметров, но с другим типом возвращаемого значе­ния, чем у виртуальной функции базового класса. Это приводит к ошибке на этапе компиляции.

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

Сказанное иллюстрирует следующая программа:

//Р10-07.СРР - некоторые особенности виртуальных функций

#include <iostream.h>

#include <conio.h>

struct base {

virtual void f1(void)

{ cout << "\nbase::f1"; }

virtual void f2(void)

{ cout << "\nbase::f2"; }

virtual void f3(void)

{ cout « "\nbase::f3"; }

};

struct dir: public base { // Виртуальная функция:

void fl(void) { cout << "\ndir::fl"; } // Ошибка в типе функции:

// int f2(void) { cout << "\ndir::f2"; } // Невиртуальная функция:

void f3(int i) ( cout << "\ndir::f3::i = "<< i; }

};

void main (void) { base B, *pb = &B;dir D, *pd = &D; pb->fl(); pb->f2(); pb->f3(); pd->fl<); pd->f2();

// Ошибка при попытке без параметра вызвать

dir::f3(int): // pd->f3(); pd->f3(0); pb = &D;pb->fl(); pb->f2(); Pb->f3();

// Ошибочное употреблениеили параметра,или указателя:

//

pb->f3(3);}

Результат выполнения программы:

base::f1 base::f2 base::f3dir::f1 base::f2dir::f3::i = 0 dir::f1 base::f2 base::f3

Обратите внимание, что три виртуальные функции базового клас­са по-разному воспринимаются в производном классе. dir::fl() -виртуальная функция, подменяющая функцию base::fl(). Функция base::f2()наследуется в классе dir так же, как и функция base::f3 (). Функция dir::f3(int) - совершенно новая компонентная функция производного класса, никак не связанная с базовым классом. Именно поэтому невозможен вызов f3 (int) через указатель на базовый класс. Виртуальные функции base::f2() и base::f3() оказались не переопределенными в производном классе dir. Поэтому при всех вызовах без параметров f3() используется только компо­нентная функция базового класса. Функция dir: :f3(int) иллюстри­рует соглашение языка о том, что если у функции производного класса набор параметров отличается от набора параметров соответ­ствующей виртуальной функции базового класса, то это не виртуаль­ная функция, а новый метод производного класса.

Завершая рассмотрение примера, еще раз подчеркнем, что при подмене виртуальной функции требуется полное совпадение сигнатур, имен и типов возвращаемых значений функций в базовом и произ­водном классах.

Как уже было упомянуто, виртуальной функцией может быть только нестатическая компонентная функция. Виртуальной не может быть глобальная функция. Функция, подменяющая виртуальную, в производном классе может быть описана как со спецификатором virtual, так и без него. В обоих случаях она будет виртуальной, т.е. ее вызов возможен только для конкретного объекта. Виртуальная функ­ция может быть объявлена дружественной (friend) в другом классе.

Механизм виртуального вызова может быть подавлен с помощью явного использования полного квалифицированного имени. Таким образом, при необходимости вызова из производного класса вирту­ального метода (компонентной функции) базового класса употреб­ляется полное имя. Например,

struct base {

virtual int f(int j) { return j * j ; }

};

struct dir: public base {

int f(int i) { /* Ваш код*/ return base::f(i * 2); }

};

Абстрактные классы. Абстрактным классом называется класс, в котором есть хотя бы одна чистая (пустая) виртуальная функция. Чистой виртуальной называется компонентная функция, которая имеет следующее определение:

virtual тип имя_функции(список_формальных_параметров) = 0;

В этой записи конструкция "= 0" называется "чистый специфика­тор". Пример описания чистой виртуальной функции:

virtual void fpure(void) = 0;

Чистая виртуальная функция "ничего не делает" и недоступна для вы­зовов. Ее назначение - служить основой для подменяющих ее функций в производных классах. Исходя из этого становится понятной невозмож­ность создания самостоятельных объектов абстрактного класса. Абстракт­ный класс может использоваться только в качестве базового для производных классов. При создании объектов такого производного класса в качестве подобъектов создаются объекты базового абстрактного класса

Предположим, что имеется абстрактный класс:

class В {

protected:

virtual void func(char) = 0;

void sos (int) ;

};

На основе класса в можно по-разному построить производные классы:

class D: public В {

void func(char);

};

class E: public В {

void sos (int) ;

};

В классе d чистая виртуальная функция func () заменена конкретной виртуальной функцией того же типа. Функция B::sos() наследуется классом D и доступна в нем и в его методах. Класс D не абстрактный. В классе е переопределена функция B::sos(), а виртуальная функция в ::func() унаследована. Тем самым класс е становится абстрактным и может использоваться только как базовый.

Как всякий класс, абстрактный класс может иметь явно определенный конструктор. Из конструктора возможен вызов методов класса, но любые прямые или опосредованные обращения из конструктора к чистым вирту­альным функциям приведут к ошибкам во время выполнения программы.

Механизм абстрактных классов разработан для представления об­щих понятий, которые в дальнейшем предполагается конкретизиро­вать. Эти общие понятия обычно невозможно использовать непо­средственно, но на их основе можно, как на базе, построить частные производные классы, пригодные для описания конкретных объектов.

Например, из абстрактного класса "фигура" можно сформировать класс "треугольник", "окружность" и т.д.

В качестве примера рассмотрим программу, в которой на основе базового класса point построен абстрактный класс figure. В классе figure определены: конструктор, чистая виртуальная функция show () для вывода изображения фигуры, например, на экран дисплея. Кроме того, в класс входят методы hideО - убрать изображение фи­гуры с экрана дисплея и move() - переместить изображение фигуры в заданную точку экрана. Функции hide()и move() обращаются к чис­той виртуальной функции show(). Однако реальное выполнение show () возможно» только после создания производного класса, в ко­тором чистая виртуальная функция show()будет подменена компо­нентной функцией для изображения конкретной фигуры.

Определение абстрактного класса figure(в файле figure. срр):

//FIGURE.CPP - абстрактный класс на базе класса

#include "point.срр"

class figure: