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

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

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

Для описания иерархии множественного наследования можно использовать прямой ациклический граф (ПАГ) (Ориентированный граф наследования без петель). Подкласс может унаследовать данные и методы одного или более родительских классов. При этом помимо спецификаторов public и 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 перенести в защищенные секции и изменить перегруженный оператор <<, как предложено выше. Т.к. защищенные экземпляры переменных одного или более базовых классов полностью доступны производным классам.