
ООП Лекции PDF / ООП 06 Лек Множественное наследование
.pdfЛекция 6 |
Множественное наследование |
1 |
Л Е К Ц И Я 6
МНОЖЕСТВЕННОЕ НАСЛЕДОВАНИЕ____________________________________________________ 1
Работа конструктора и деструктора ________________________________________________________ 1 Пример множественного наследования_____________________________________________________ 1 Виртуальные базовые классы_____________________________________________________________ 3
МНОЖЕСТВЕННОЕ НАСЛЕДОВАНИЕ
Если у производного класса имеется несколько базовых классов, то говорят о множественном наследовании. Множественное наследование позволяет сочетать в одном производном классе свойства и поведение нескольких классов.
Работа конструктора и деструктора
Рассмотрим пример, который демонстрирует порядок вызова конструкторов и деструкторов базовых классов.
#include <iostream.h>
class Base1 |
|
|
|
// базовый класс |
{ |
|
|
|
|
public : |
|
|
|
|
Base1 ( ) |
{ |
cout <<"Constructor Base1 \n"; } |
// конструктор базового класса |
|
~Base1 ( ) { |
cout <<"Destructor Base1 \n"; |
} |
// деструктор базового класса |
|
} |
|
|
|
|
class Base2 |
|
|
|
// базовый класс |
{ |
|
|
|
|
public : |
|
|
|
|
Base2 ( ) |
{ |
cout <<"Constructor Base2 \n"; } |
// конструктор базового класса |
|
~Base2 ( ) { |
cout <<"Destructor Base2 \n"; |
} |
// деструктор базового класса |
|
} |
|
|
|
|
class Derived : public Base1 , public Base2 |
// производный класс – наследник классов Base1 и Base2 |
|||
{ |
|
|
|
|
public : |
|
|
|
|
Derived ( ) |
{ cout <<"Constructor Derived \n";} |
// конструктор производного класса |
||
~Derived ( ) { cout <<"Destructor Derived \n" ; } |
// деструктор производного класса |
|||
} |
|
|
|
|
void main ( ) |
|
|
|
|
{ |
|
|
|
|
Derived A ; |
|
|
// создание объекта производного класса |
|
} |
|
|
|
|
Эта программа выводит на экран следующее:
Constructor Base1
Constructor Base2
Constructor Derived
Destructor Derived
Destructor Base2
Destructor Base1
Этот пример наглядно демонстрирует, что конструкторы базовых классов вызываются в порядке их объявления. Деструкторы вызываются в обратном порядке.
Пример множественного наследования
Класс вектора, приведённого в предыдущей лекции, имеет небольшой недостаток. Мы зарезервировали для имени вектора строку длиной три байта, которая может хранить два символа (третий – нультерминатор). Если пользователь создаст объект вектора и передаст конструктору в качестве имени строку, состоящую из трёх или более символов, то имя вектора будет некорректно отображаться на экране. Задачу можно решить путём отсечения лишних символов. Может оказаться целесообразным решать эту задачу в отдельном абстрактном классе имени, а класс вектора унаследует данные и методы этого класса.
Класс имени Name хранит строку из двух символов и обеспечивает её корректный ввод и вывод.
Выжол Ю.А. |
Объектно-ориентированное программирование |
Лекция 6 |
Множественное наследование |
2 |
class Name |
|
|
{ |
|
|
char name [ 3 ] ; |
|
// имя |
public: |
|
|
Name ( char* pName ) { strncpy ( name , pName , 2 ) ; name [ 2 ] = '\0' ; } |
// конструктор |
|
void Print ( ) { cout<<name ; } |
|
// вывод имени на экран |
}; |
|
|
Класс координат Coord мы оставим без изменений. Класс вектора Vec наследует данные и методы касса имени Name и класса координат Coord и имеет вид:
class Vec : public Name , public Coord
{
public:
// конструкторы производного класса |
|
|
Vec ( char* pName ) : |
Name ( pName ) , Coord ( ) |
{ } |
Vec ( char* pName , double X , double Y ) : Name ( pName ) , Coord ( X , Y ) |
{ } |
|
// переопределённые функции-члены |
|
|
void Print ( ) ; |
// вывод на экран имени и координат |
|
void Input ( ) ; |
// вывод приглашения и ввод координат с клавиатуры |
|
}; |
|
|
void Vec :: Print ( ) |
|
|
{ |
|
|
char S [ ] = "Проекции вектора "; // объявление и инициализация строки заголовка |
||
CharToOem ( S , S ) ; |
// преобразование строки в символы кириллицы |
|
cout<<S ; |
// вывод на экран заголовка |
|
Name :: Print ( ) ; |
// вывод на экран имени вектора |
|
cout<<" :"; |
// вывод на экран двоеточия |
|
Coord :: Print ( ) ; |
// вывод значений проекций на экран |
|
} |
|
|
void Vec :: Input ( )
{
char S [ ] = "Введите проекции вектора "; // объявление и инициализация строки заголовка
CharToOem ( S , S ) ; |
// преобразование строки в символы кириллицы |
cout<<S ; |
// вывод на экран заголовка |
Name :: Print ( ) ; |
// вывод на экран имени вектора |
cout<<'\n'; |
// переход на другую строку |
Coord :: Input ( ) ; |
// ввод значений проекций с клавиатуры |
}
Этот пример демонстрирует, как осуществляется передача параметров конструкторам базовых классов. При множественном наследовании, как и при простом, конструкторы базовых классов вызываются компилятором до вызова конструктора производного класса. Единственная возможность передать им аргументы – использовать список инициализации элементов. Причем порядок объявления базовых классов при наследовании определяет и порядок вызова их конструкторов, которому должен соответствовать порядок следования конструкторов базовых классов в списке инициализации. В данном случае класс Vec содержит такое объявление о наследовании:
class Vec : public Name , public Coord
Этому объявлению соответствует следующий конструктор:
Vec ( char* pName , double X , double Y ) : Name ( pName ) , Coord ( X , Y ) { }
В основной программе этому конструктору передаются аргументы при создании объекта класса Vec. Первым вызывается конструктор класса Name, который получает в качестве аргумента указатель на строку имени. Далее вызывается конструктор класса Coord, который получает в качестве аргументов значения координат. Сам конструктор класса Vec, никаких действий не выполняет.
Обратим внимание на то, как реализована функция печати параметров вектора Vec::Print ( ). Эта функция выводит на экран заголовок и вызывает унаследованные от базовых классов функцию печати имени точки Name :: Print ( ) и функцию печати координат точки Coord :: Print ( ).
Таким образом, множественное наследование позволяет использовать уже существующий код и избежать его дублирования.
Выжол Ю.А. |
Объектно-ориентированное программирование |
Лекция 6 |
Множественное наследование |
3 |
Виртуальные базовые классы
В сложной иерархии классов при множественном наследовании может получиться так, что производный класс косвенно унаследует два или более экземпляра одного и того же класса. Рассмотрим пример, который проясняет эту ситуацию.
class Ground |
// базовый класс |
{ |
|
int x ; |
|
public : |
|
int GetX ( ) { return x ; } |
|
void SetX ( int X ) { x = X ; } |
|
} |
|
class Basel : public Ground |
// наследует базовый класс Ground |
{ |
|
• • • |
|
} |
|
class Base2 : public Ground |
// наследует базовый класс Ground |
{ |
|
• • • |
|
} |
|
class Derived : public Base1 , public Base2 |
// производный класс – наследник классов Base1 и Base2 |
{ |
|
• • • |
|
} |
|
void main ( ) |
|
{ |
|
Derived ob ; |
// создание объекта производного класса |
ob.SetX ( 1 ) ; |
|
int z = A.GetX ( ) ; |
|
} |
|
Здесь класс Derived косвенно наследует класс Ground через свои базовые классы Base1 и Base2. Поэтому при компиляции приведенного примера возникнут ошибки, вызванные неоднозначностью обращения к членам класса GetX ( ) в строках:
ob.SetX ( 0 ) ;
int z = A.GetX ( ) ;
Чтобы избежать этой неоднозначности, можно использовать квалификацию имен, применив операцию разрешения видимости:
ob.Base1 :: SetX ( 1 ) ;
int z = A.Base1 :: GetX ( ) ;
Можно также квалифицировать эти вызовы следующим образом:
ob.Base2 :: SetX ( 1 ) ;
int z = A.Base2 :: GetX ( ) ;
Хотя этот способ и позволяет избежать неоднозначности при вызове, тем не менее, класс Ground будет включен в состав класса Derived дважды, увеличивая его размер. Избежать повторного включения косвенного базового класса в производный класс можно, дав указание компилятору использовать виртуальный базовый класс. Это осуществляется с помощью ключевого слова virtual, которое указывается перед спецификатором наследуемого доступа или после него. Следующий пример является модифицированным вариантом предыдущего, использующим класс Ground в качестве виртуального базового класса.
class Ground |
// базовый класс |
{ |
|
int x ; |
|
public : |
|
int GetX ( ) { return x ; } |
|
void SetX ( int X ) { x = X ; } |
|
} |
|
Выжол Ю.А. |
Объектно-ориентированное программирование |
Лекция 6 |
Множественное наследование |
4 |
|
class Basel : virtual public Ground |
|
// наследует класс Ground как виртуальный класс |
|
{ |
|
|
|
• • • |
|
|
|
} |
|
|
|
class Base2 : virtual public Ground |
|
// наследует класс Ground как виртуальный класс |
|
{ |
|
|
|
• • • |
|
|
|
} |
|
|
|
class Derived : public Base1 , public Base2 |
// производный класс – наследник классов Base1 и Base2 |
||
{ |
|
|
|
• • • |
|
|
|
} |
|
|
|
void main ( ) |
|
|
|
{ |
|
|
|
Derived ob ; |
|
// создание объекта производного класса |
|
ob.SetX ( 1 ) ; |
|
|
|
int z = A.GetX ( ) ; |
|
|
|
} |
|
|
|
В этом случае класс Derived содержит один экземпляр класса Ground, и вызовы
ob.SetX ( 1 ) ;
int z = A.GetX ( ) ;
не приводят к появлению сообщений компилятора об ошибках, связанных с неоднозначностью.
Отсюда следует общая рекомендация: если разрабатывается иерархия классов и данный класс наследуется несколькими классами, включите перед спецификаторами наследуемого доступа ключевое слово virtual, то есть наследуйте его как виртуальный базовый класс.
Наконец, отметим один важный момент. В случае, если класс имеет один или несколько базовых классов, их конструкторы вызываются перед вызовом конструктора производного класса. Конструкторы базовых классов вызываются в том порядке, в котором они объявлены в списке наследования.
Объявление базового класса виртуальным изменяет порядок вызова конструкторов при создании экземпляра производного класса. Конструкторы виртуальных базовых классов вызываются первыми, раньше конструкторов невиртуатьных базовых классов. Если виртуальных базовых классов несколько, их конструкторы вызываются в порядке их объявления в списке наследования. Затем вызываются конструкторы невиртуальных базовых классов в порядке их объявления в списке наследования и, наконец, вызывается конструктор производного класса. Если какой-то виртуальный класс является производным невиртуального базового класса, этот невиртуальный базовый класс конструируется первым (иначе нельзя будет вызвать конструктор виртуального базового класса).
Если иерархия классов содержит несколько экземпляров виртуального базового класса, этот базовый класс конструируется только один раз. Если все же существуют как виртуальный, так и невиртуальный экземпляры этого базового класса, конструктор базового класса вызывается один раз для всех виртуальных экземпляров этого базового класса, а затем еще раз для каждого невиртуального экземпляра этого базового класса.
Деструкторы вызываются в порядке, в точности обратном конструкторам.
Выжол Ю.А. |
Объектно-ориентированное программирование |