Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТП лекции Раздел 4.doc
Скачиваний:
16
Добавлен:
28.09.2019
Размер:
2.56 Mб
Скачать

4.8.3. Типы наследования.

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

class Basel

{

// Компоненты первого базового класса

};

class Base2

{

// Компоненты второго базового класса

}; ,

class Derived: public Basel, private Base2

{

// Компоненты производного класса

};

Во всех рассмотренных примерах производный класс был образован от од­ного базового класса — так называемое единичное наследование. Однако ме­ханизм наследования в C++ более развит и включает наряду с единичным и множественное наследование, при котором производный класс образуется сразу от нескольких базовых, что позволяет создавать достаточно сложные иерархии классов (Графовая иерархия классов).

В объявлении класса может быть записано любое количество базовых клас­сов, перечисленных через запятую. Порядок, в котором перечисляются ба­зовые классы, произвольный. Он влияет только на последовательность вы­зова конструкторов, которая является следующей:

  1. Сначала вызываются конструкторы всех виртуальных базовых классов; если их несколько, то конструкторы вызываются в порядке их наследо­вания.

  2. Затем конструкторы не виртуальных базовых классов в порядке их насле­дования.

  3. И, наконец, конструкторы всех компонентных классов.

Как обычно, последовательность вызова деструкторов является обратной относительно последовательности вызовов конструкторов.

При множественном наследовании атрибуты доступа (прав пользования) указы­ваются отдельно для каждого родительского класса. Например:

class А : private В. protected С. D, public E {...};

Все данные и методы класса В наследуются классом А с правами private, члены класса С наследуются им же с правами protected, члены класса D насле­дуются с правами private (по умолчанию), а члены класса Е — с правами public. Таким образом, protected- и public-члены класса С попадают в секцию protected класса А. Все члены классов В и D попадают в секцию private класса А. Все члены класса Е наследуются с сохранением прав доступа. Наследованные клас­сом А private-члены всех классов, как было показано выше, доступны только с помощью методов, унаследованных классом А от своих классов.

Поскольку множественное наследование является более сложной конструк­цией по сравнению с единичным, то вполне очевидно, что при этом возни­кают и некоторые проблемы.

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

class A {...};

class B : public A {...};

class С : public A {...};

class D : public A. public B, public С {...};

Здесь каждый объект класса D содержит наследованные члены класса А в трех экземплярах. Если это не нужно и мешает работе с объектом, то следует добавить описатель virtual при указании родительского класса:

class A {...};

class В : public virtual A {...};

class С : public virtual A {...};

class D : public A, public B, public C {...};

Теперь члены виртуального класса будут унаследованы классом D только в одном экземпляре.

В случае множественного наследования вполне вероятно, что один и тот же класс может унаследовать несколько объектов с одним и тем же именем из разных классов:

class Basel

{

int nObj; // Остальные компоненты первого базового класса

};

class Base2

{

int nObj;

// Остальные компоненты второго базового класса };

class Derived: public Basel, public Base2

{

// Компоненты производного класса

} clDer;

В данном случае имеется 2 объекта clDer.nObj с одним и тем же именем, а значит обращаться к ним просто по имени нельзя. Выход – в использовании оператора разрешения контекста и полных имен объектов:

clDer.BaseClass1::nObj++;

clDer.BaseClass2::nObj++;

Такое же решение проблемы необходимо использовать в случае наследова­ния через промежуточный класс:

class BaseClass

{

int nBaseObj;

};

class FirstBaseClass: public BaseClass

{

int nFirstObj;

};

class SecondBaseClass: public BaseClass

{

int nSecondObj;

};

class DerivedClass: public FirstBaseClass, public SecondBaseClass

{

long nDerivedObj;

// Остальные компоненты производного класса

} clDer;

clDer.FirstBaseClass::nObj++;

clDer.SecondBaseClass::nObj ++;

При использовании наследования через промежуточный класс возникает другая проблема — при приведении типа указателя одного класса к другому может возникнуть неоднозначность:

DerivedClass clDerived;

DerivedClass *ptrDer = SclDerived;

BaseClass *ptrBase;

ptrBase = (Base *)ptrDer; // Ошибка — неоднозначность приведения типа

Проблема решается выполнением явного приведения типа к классу, для ко­торого неоднозначностей не возникает

ptrBase = (Base *)(FirstBase *)ptrDer; // Теперь правильно

Тема 4.9. Полиморфизм в Visual C++.