Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Первый пример

Пример

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

 

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

 

 

#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 ",i);

}

};

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

base::base(int x)

{

i=x;

return;

};

class der1: public base {

public:

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

void func()

{

printf("Функция из производного класса %d ", 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 -производного класса, как и при одиночном наследовании, определяют производные классы, объекты которых имеют простой доступ к открытым данным или функциям-членам базового класса.

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

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

 

 

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

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

 

 

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

Класс Derived является производным от обоих базовых классов Base1 и Base2.

_______________________________________________________

class Base1 {

int id;

public:

Base1(void) {

cout << “ Constructor Base1”;

id=0;

}

Base1(int anid) {

cout << “ Constructor Base1”;

id=anid;

}

void assignid(int anid) {

id=anid;

}

int accessid(void) {

return id;

}

};

________________________________________________________

class Base2 {

char name[20];

public:

Base2(void) {

cout << “ Constructor Base2”;

strcpy(name,”void”);

}

Base2(char *str) {

cout << “ Constructor Base2”;

strcpy(name,str);

}

void assignName(char *str){

strcpy(name,str);

}

char *accessName(void) { return name; }

};

__

_____________________________________________________

class Derived: public Base1,public Base2 {

char ch;

public:

Derived(void) {

cout << “ Construct Derived”;

ch=‘a’;

}

Derived(char c,int anid,char *str):Base1(anid),Base2(str) {

cout << “ Construct Derived”;

ch=c;

}

void assign(char c) {

ch=c;

}

friend ostream& operator<<(osream& o,Derived& d);

};

ostream& operator<<(ostream& o,Derived& d) {

o<<“id”<<d.accessid()<<“name”<<d.accessName() <<“ ch”<<c;

return o;

}

_________________________________________________

main() {

Derived object1;

cout << object1;

Derived object2(‘e’,26,”Robert”);

cout << object2;

}

Результаты работы программы

Constructor Base1 Constructor Base2 Construct Derived id 0 name void ch a

Constructor Base1 Constructor Base2 Construct Derived

id 26 name Robert ch e

 

 

Сначала создается класс Base1, содержащий закрытую целую переменную, два инициализирующих конструктора (по умолчанию и с параметром), показывающих свой вызов выводом соответствующей строки, и две интерфейсные функции (чтения и записи). Класс Base2 имеет аналогичную функциональность для закрытого символьного массива.

В объявлении производного класса после двоеточия перечисляются все предки этого класса через запятую. Конструктор по умолчанию инициализирует скрытый символ ch, а конструктор с параметрами сопровождается вызовом конструкторов с параметрами классов предков (они перечисляются после двоеточия). Вызов конструктора так же сопровождается выводом надписи на экран. Кроме этого, определяется дружественный оператор вставки в поток <<, который осуществляет вставку в поток ostream данных класса Derived.

В функции main последовательно создается один объект и вывод данных объекта по умолчанию object1 и один объект и вывод данных объекта с параметрами object2.

 

Когда object1 объявлен при помощи выражения Derived object1, вызываются void -конструкторы двух базовых классов в той последовательности, в которой определено наследование (Base1 -конструктор первый, Base2- конструктор второй).

Во втором случае при создании экземпляра object2 вызов конструкторов с параметрами родительского класса так же определяется порядком наследования, т.е. сначала Base1 (anid)затем Base2(str) и Derived(char c,int anid,char *str)

________________________________________________________

Возможны варианты

1. Что, если переписать конструктор Derived c тремя параметрами, как приведено ниже:

 

Derived(char c,int anint,char *str): Base2(str),Base1(anint) { ch=c; }

 

Результат работы программы будет таким же.

 

2. Что, если переписать перегруженный оператор <<, как показано:

 

ostream & operator <<(ostream&o,Derived& d) {

o<<“id”<<d.id<<“name”<<d.name<<“ ch”<<d.c; return o;

}

 

Компилятор выдаст сообщения об ошибке. Производный класс в иерархии множественного наследования не имеет доступа к защищенным экземплярам переменных любых его базовых классов. Оператор вывода << является дружественным классу Derived, но не Base1 и Base2. Поэтому он не имеет прямого доступа к защищенным членам Base1 и Base2.

 

Для корректной работы программы необходимо экземпляры переменных в классах Base1 и Base2 перенести в защищенные секции и изменить перегруженный оператор <<, как предложено выше. Т.к. защищенные экземпляры переменных одного или более базовых классов полностью доступны производным классам.

 

<== предыдущая лекция | следующая лекция ==>
Встраиваемые функции inline | В стандартных условиях энтропия простого вещества не равна нулю
Поделиться с друзьями:


Дата добавления: 2014-01-13; Просмотров: 341; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.049 сек.