Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Козак Н.В. Лекции Основы создания программ в Си...doc
Скачиваний:
31
Добавлен:
23.09.2019
Размер:
2.24 Mб
Скачать

Сложное наследование

Язык C++ допускает не только простое, но и сложное наследование, т.е. наследование от двух и более непосредственных базовых классов. Это позволяет создавать классы, комбинирующие в себе свойства нескольких независимых классов-предков.

Это чаще всего имеет смысл, когда у вас есть некоторый набор понятий, которые могут более или менее независимо комбинироваться с различными элементами другого набора понятий. Простейший пример. Имеется понятие «растение». Бывают «культурные растения», и бывают «дикорастущие растения». С другой стороны, растение может иметь или не иметь «товарной ценности», т.е. быть полезным или бесполезным с коммерческой точки зрения. Если говорить о товарной ценности, то тут у растений бывают «цветы» и «плоды» и т.д. Все это образует довольно развернутую структуру, которая может порождать понятия вроде «дикое растение, цветы которого можно продавать на рынке». (Возможно, кстати, и такое: «дикое растение, цветы которого имеют товарную ценность, но которые нельзя продавать на рынке»!) А можно, с некоторыми модификациями, говорить то же самое не о растениях, а о животных или веществах, минералах. И есть не только «товарные» сущности, но и сорняки, вредители. И так далее.

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

//Производный класс сообщений таймера.

class Alarm: public Time, public Message

{

public:

Alarm(char* str, int h, int m): Time(h, m), Message(str)

{}

void show();

};

Конструктор производного класса Alarm имеет пустое тело и список инициализации, вызывающий конструкторы базовых классов.

Неоднозначности при сложном наследовании

В иерархии классов со сложным наследованием вполне может получиться так, что класс косвенно унаследует несколько экземпляров некоторого базового класса. Если В и С оба являются наследниками A, a D наследует В и С, то D получает двойной набор элементов класса А. Это может приводить к неоднозначностям при обращении к ним, что будет вызывать ошибки времени компиляции. Вот иллюстрация:

class A

{

public:

int aData;

void aFunc ();

// . . .

};

class B: public A {// ...};

class C: public A {// . . .};

class D: public B, public С // Двукратно наследует А.

{// ...}

int main(void)

D d;

d.aData =0; // Ошибка!

d.aFunc(); // Ошибка!

return 0;

}

В этом примере строки в main (), содержащие обращения к унаследованным от А элементам, будут вызывать ошибку компиляции с выдачей сообщения о том, что элемент класса неоднозначен. Однако эту неоднозначность несложно устранить, применив операцию разрешения области действия, например, так:

d.B::aData = 0;

d.C::aFunc() ;

Виртуальные базовые классы

В качестве альтернативы применению операции разрешения области действия при сложном наследовании, подобном описанному в предыдущем параграфе, можно потребовать, чтобы производный класс содержал только одну копию базового. Этого можно достигнуть, описав базовый класс при наследовании от него как виртуальный с помощью ключевого слова virtual. Вот модификация предыдущего примера, которая делает класс А виртуальным базовым классом:

class A

{

public:

int aData;

void aFunc();

// .. .

}

class B: public virtual A //A - виртуальный базовый класс.

// . . .

class С: public virtual A //A - виртуальный базовый класс.

// ...

class D: public B, public С // Содержит только одну копию А.

// . . .

int main(void)

{

D d;

d.aData = 0; // Теперь неоднозначности не возникает,

d.aFunc (); //

return 0;

}