Виртуальные функции и абстрактные классы
К механизму виртуальных функций обращаются в тех случаях, когда в базовый класс необходимо поместить функцию, которая должна по-разному выполняться в производных классах. Точнее, по-разному должна выполняться не единственная функция из базового класса, а в каждом производном классе требуется свой вариант этой функции.
Например, базовый класс может описывать фигуру на экране без конкретизации ее вида, а производные классы (треугольник, эллипс и г.п.) однозначно определяют ее формы и размеры. Если в базовом классе ввести функцию для изображения фигуры на экране, то выполнение этой функции будет возможно только для объектов каждого из производных классов, определяющих конкретные изображения.
До объяснения возможностей виртуальных функций отметим, что классы, включающие такие функции, играют особую роль в объектно-ориентированном программировании. Именно поэтому они носят специальное название - полиморфные.
Рассмотрим теперь, как ведут себя при наследовании невиртуальные компонентные функции с одинаковыми именами, типами и сигнатурами параметров.
Если в базовом классе определена некоторая компонентная функция, то такая же функция (с тем же именем, того же типа и с тем же набором и типами параметров) может быть введена в производном классе. Рассмотрим следующее определение классов:
В данном случае внешне одинаковые функции void fun (int) определены в базовом классе base и в производном классе dir.
В теле класса dir обращение к функции fun (), принадлежащей классу base, может быть выполнено с помощью полного квалифицированного имени, явно включающего имя базового класса: base::fun(). При обращении в классе dir к такой же (по внешнему виду) функции, принадлежащей классу dir, достаточно использовать имя fun() без предшествующего квалификатора.
В программе, где определены и доступны оба класса base и dir, обращения к функциям fun() могут быть выполнены с помощью указателей на объекты соответствующих классов:
//Одинаковые функции в базовом и производном классах
#include "stdafx.h"
#include <iostream>
using namespace std;
struct base {
void fun(int i) { cout << "\nbase::i = " << i; }
};
struct dir: public base{
void fun (int i){ cout << "\ndir::i - " << i; }
};
void print_(base* pb){pb->fun(1);}
void main (void){
base B, *bp=&B;
dir D, *dp = &D;
base *pbd = &D;
bp->fun(1); // Печатает : base::i = 1
dp->fun(5); // Печатает : dir::i = 5
pbd->fun(4); // Печатает : base::i = 4
print_(&B); // Печатает : base::i = 1
print_(&D); // Печатает : base::i = 1
}
Результаты выполнения программы:
base::i = 1 dir::i = 5 base::i = 4
В программе введены три указателя на объекты разных классов. Следует обратить внимание на инициализацию указателя pbd. В ней адрес объекта производного класса (объекта D) присваивается указателю на объект его прямого базового класса (base *). При этом выполняется стандартное преобразование указателей, предусмотренное синтаксисом языка Си++. Обратное преобразование, т.е. преобразование указателя на объект базового класса в указатель на объект производного класса, невозможно (запрещено синтаксисом). Обращения к функциям классов base и dir с помощью указателей bр и dp не представляют особого интереса. Вызов pbd->fun() требуется прокомментировать. Указатель pbd имеет тип base*, однако его значение - адрес объекта D класса dir.
Какая же из функций base: :fun() или dir: :fun() вызывается при обращении pbd->fun()? Результат выполнения программы показывает, что вызывается функция из базового класса. Именно такой вызов предусмотрен синтаксисом языка Си++, т.е. выбор функции (не виртуальной) зависит только от типа указателя, но не от его значения. "Настроив" указатель базового класса на объект производного класса, не удается с помощью этого указателя вызвать функцию из производного класса.
Вернемся к упомянутому выше примеру с фигурой в виде базового класса с названием figure. Пусть в этом классе определена компонентная функция void show (). Так как внешний вид фигуры в базовом классе еще не определен, то в каждый из производных классов нужно включить свою функцию void show() для формирования изображения на экране. Если оставаться в рамках проиллюстрированного в примере с классами base и dir механизма, то доступ к функции show () производного класса возможен только с помощью явного указания области видимости:
Дата добавления: 2020-12-11; просмотров: 370;