Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Вторая часть лекции.doc
Скачиваний:
0
Добавлен:
01.04.2025
Размер:
592.9 Кб
Скачать

Имя_проиаводного_класса: : show ()

либо с использованием имени конкретного объекта:

Иня_объекта_производноро_класса. Show ()

В обоих случаях выбор нужной функции выполняется при написа­нии исходного текста программы и не изменяется после компиляции. Такой режим называется ранним или статическим связыванием.

Большую гибкость (особенно при использовании уже готовых би­блиотек классов) обеспечивает позднее (отложенное), или динамиче­ское связывание, которое предоставляется механизмом виртуальных функций. Любая нестатическая функция базового класса может быть сделана виртуальной, если в ее объявлении использовать специфика­тор virtual. Прежде чем объяснить преимущества динамического свя­зывания, приведем пример. Опишем в базовом классе виртуальную функцию и введем два производных класса, где определим функции с такими же прототипами, но без спецификатора virtual. В следующей программе в базовом классе base определена виртуальная функция void vfun(int). В производных классах dirl, dir2 эта функция подменяется (override), т.е. определена по-другому:

//Р10-06.СРР - виртуальная функция в базовом классе

#include <iostream.h>

#include <conio.h>

struct base {

virtual void vfun(int i) { cout << "\nbase::i - " << i; }

};

struct dir1: public base {

void vfun (int i){ cout << "\ndirl::i - " << i; }

};

struct dir2: public base {

void vfun (int i){ cout << "\ndir2::i = " << i; }

};

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::f3 dir::f1 base::f2 dir::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;