Студопедия
Случайная страница | ТОМ-1 | ТОМ-2 | ТОМ-3
АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатика
ИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханика
ОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторика
СоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансы
ХимияЧерчениеЭкологияЭкономикаЭлектроника

Виртуальные функции.

Читайте также:
  1. Агрегатные функции.
  2. Альвеоциты I типа. Особенности строения, функции. Особенности энергетического обмена. Механизм секреции воды.
  3. Бесконечно большие и бесконечно малые функции.
  4. Виртуальные исследования
  5. Виртуальные исследования
  6. Виртуальные исследования
  7. Виртуальные исследования

 

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

 

 

#include <stdio.h>

class base {

public:

int i;

base(int x); //конструктор

void func()

{

printf("Базовая функция %d",i);

}

};

//текст конструктора

base::base(int x)

{

i=x;

return;

}

 

class der1: public base {

public:

der1(int x) :base(x) {}; //конструктор с вызов конструкт базового класса с параметром

void func()

{

printf("Функция из производного класса %d", i*i);

}

};

main()

{

base * pc; //указатель на базовый класс

base ob(2); //создать экземпляр объекта базового класса

der1 ob1(2); //создать экземпляр объекта производного класса

pc=&ob; //указатель на объект базового класса

pc->func(); //вызов функции базового класса

pc=&ob1; //указатель на объект производного класса

pc->func(); //попытка вызова функции производного класса

return 0;

}

Может показаться, что в первом случае будет вызываться функция базового класса, а во втором функция производного. Однако при проверке будет видно, что и в том и в другом случае будет вызвана функция базового класса.

Дело в том, что компилятору трудно понять, какая реально функция имеется в виду и он на стадии компилирования подставляет во всех тех случаях, где встречается имя func() адрес функции базового класса. Такой процесс установки адресов называется "ранним связыванием". Иногда употребляется термин "статическое связывание". Если же мы хотим, чтобы во втором случае, т.е. когда указатель pc указывал на производный класс вызывалась функция этого класса, ее еще в базовом классе следует указать как виртуальную. В данном случае вместо строки void func() следует написать virtual void func(). После этого наш пример будет работать как надо.

Ситуация похожа на проблему перегрузки. Однако перегружаемые функции отличаются друг от друга типом или аргументами, здесь же функции должны быть идентичны.

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

 

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

Пример

#include <stdio.h>

class base {

public:

int i;

base(int x); //конструктор

virtual void func()

{

printf("Базовая функция %d\n",i);

}

};

//текст конструктора

base::base(int x)

{

i=x;

return;

};

class der1: public base {

public:

der1(int x) :base(x) {}; //конструктор

void func()

{

printf("Функция из производного класса %d\n", i*i);

}

};

class der2: public base {

public:

der2(int x) :base(x) {}; //конструктор

};

 

main()

{

base * pc; //указатель на базовый класс

base ob(2); //создать экземпляр объекта базового класса

der1 ob1(2); //создать экземпляр объекта производного класса 1



der2 ob2(2); //создать экземпляр объекта производного класса 2

pc=&ob; //указатель на объект базового класса

pc->func(); //вызов функции базового класса

pc=&ob1; //указатель на объект производного класса 1

pc->func(); //попытка вызова функции производного класса

pc=&ob2; //указатель на объект производного класса 2

pc->func(); //попытка вызова функции производного класса

return 0;

}

В данном случае вводится еще один производный класс. В нем функция func() не определена. В этом случае будет вызываться функция класса родителя. Т.е. появится строка: Базовая функция 2.

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

Если же необходимо, чтобы для класса объектов der2 вызывалась функция класса der1, следует сделать класс der2 наследником не класса base, а класса der1.

В производных классах функция, определенная в базовом классе как виртуальная может определяться, а может и нет. Если необходимо, чтобы во всех производных классах обязательно была определена виртуальная функция, то в базовом классе ее надо определить следующим образом:

Загрузка...

 

virtual void func() = 0;

 

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

 

Производные классы: множественное наследование

 

Иерархию простого наследования можно описать, используя структуру дерева, где каждый узел представляет подкласс, который может порождать любое количество дополнительных подклассов. Как и в случае простого наследования, определители private, protected, public в родительском классе можно использовать для управления доступом к экземплярам переменных и методам, которые унаследованы производным классом (подклассом) от базового (родительского) класса. Кроме того, спецификатор public или private-производного класса, как и при одиночном наследовании, определяют производные классы, объекты которых имеют простой доступ к открытым данным или функциям-членам базового класса.

Для описания иерархии множественного наследования можно использовать прямой ациклический граф (ПАГ) (Ориентированный граф наследования без петель). Подкласс может унаследовать данные и методы одного или более родительских классов. При этом помимо спецификаторов public и private-производных классов, используется дополнительная опция virtual.

Преимущества и недостатки каждого из видов наследования видны из этих сравнительных примеров:

 

 

В случае наследования классов библиотеки VCL накладывается ограничение на множественное наследование, т.е. у любого класса этой библиотеки может быть один единственный предок на соседнем верхнем уровне иерархии. Несмотря на то, что в этом случае нет путаницы относительно дублирования наследуемых свойств и методов, видно, что появляется избыточность кода и большее количество предков в иерархии. Т.к. некоторые компоненты имеют ближайших аналогов в нескольких параллельных ветвях графа, а поскольку множественное наследование запрещено, приходится вместо наследования необходимых свойств и методов у аналогов вносить дополнительный программный код и включать в число предков классы, относящиеся к данной иерархии, даже если их свойства нет необходимости наследовать. Например, при создании кнопки с изображением нужны свойства 2-х классов TButton и TGraphicControl. В случае множественного наследования можно было бы сделать проектируемый класс наследником этих 2-х. Однако, при запрещении множественного наследования приходится выбирать от какого класса каких свойств и методов наследуется больше и сделать наследование от него, а остальную часть кода вносить в класс дополнительно.

Использование множественного наследования имеет свои преимущества и недостатки. Например, имеется задача создать следующую иерархию классов:

 

 

Свойства и методы необходимые для создания проектируемого класса задаются в соответствии с логикой развития и появления необходимых качеств класса. Однако в случае дублирования этих свойств и методов происходит их дублирование в наследовании, т.е. в дочернем классе всегда будут присутствовать одни и те же копии свойств и методов родительских классов, что может порождать неоднозначность их трактовки. Например, свойства скорость и грузоподъемность будут унаследованы классами гидроплан и его потомками в двух экземплярах, причем отличить их в случае одинаковых имен компилятору будет невозможно.


Дата добавления: 2015-07-07; просмотров: 237 | Нарушение авторских прав


Читайте в этой же книге: Средства реализации программного кода | Лекция 2. История развития ООП и сред RAD | Отличия ООП от процедурных программ | Пример для освежения в памяти | Лекция №5 Данные-элементы, статические данные, константные данные | Функции-элементы, дружественные функции, константные функции |
<== предыдущая страница | следующая страница ==>
Встраиваемые функции inline| Первый пример

mybiblioteka.su - 2015-2021 год. (0.013 сек.)