4.5. Множественное наследование

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

При инициализации объекта производного класса сначала вызываются конструкторы базовых классов, в порядке их перечисления в объявлении производного класса, а потом – конструктор производного класса.

Пример. Пусть INTEGER – класс, объектами которого являются целые числа, POSITIVE – класс положительных целых чисел. Определим рациональную дробь как объект производного класса от этих двух классов. Пара, представляющая рациональную дробь, состоит из взаимно простых целых чисел.

#include <iostream.h>    //библиотека потокового ввода-вывода

#include <process.h>    //библиотека с прототипом функции exit

#include <conio.h>      //библиотека консольного ввода-вывода

class INTEGER           //класс целых чисел

{

public:

long NUM;               //информационное поле

INTEGER (long Val): NUM(Val) {} //конструктор

};

class POSITIVE          //класс положительных чисел

{

public:

unsigned long Den;      // информационное поле

POSITIVE(unsigned long d) : Den(d) //конструктор

{

       if(d==0) {cout << "Ошибка"; exit(1);}//ноль недопустим

}

};

class RATIONAL : public INTEGER, public POSITIVE

//класс дроби

{

//дружественная функция вывода дроби в некоторый поток

friend ostream &operator<<(ostream& stream, RATIONAL& o);

public:

RATIONAL(long v, unsigned long u=1): INTEGER(v), POSITIVE(u)

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

{

long w;

if (v==0) {u=1; return;}

if(v<0) {w = -v;}

else

       {

       w=v;

       }

//поскольку числитель и знаменатель должны быть

//взаимно простыми числами то следует найти наибольший

//общий делитель для числителя и знаменателя

while (w!=u)

{

       if(w>u) w=w-u;

       if(u>w) u=u-w;

}

//и следует сократить дробь

NUM = NUM/w;

Den = Den/w;

}

};

ostream& operator<<(ostream& stream, RATIONAL& o)

{

stream<<o.NUM<<"/"<<o.Den;

        return stream;

}

main()

{

RATIONAL r1(10, 20), r2(-15, 10);

  clrscr();

cout<<"Первая дробь (числитель равен 10, знаменатель равен 20): ";

cout<<r1<<"n";

cout<<"Вторая дробь (числитель равен -15,знаменатель равен 10): ";

cout<<r2<<"n";

getch();

}

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

Первая дробь (числитель равен 10, знаменатель равен 20): 1/2

Вторая дробь (числитель равен -15,знаменатель равен 10): -3/2

В данном примере при инициализации объекта – рационального числа – сначала будет вызван конструктор INTEGER, затем – конструктор класса POSITIVE, затем – конструктор класса RATIONAL.

Доступ к членам базовых классов, имеющих одинаковые имена, осуществляется через имена базовых классов, которым они принадлежат, при помощи операции разрешения доступа. Например:

Class A

{

public: void f();

};

class B

{

public: void f();

};

class C : public A, public B {};

main()

{

C c;

c.f();

 // ошибка – неизвестно, какая из функций вызывается A::f() или B::f()

c.A::f(); // правильный вызов

}

На рис. 4.1 приведена иерархическая структура, иллюстрирующая множественное наследование из приведённого выше примера.

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

Class C : public A, public B

{

public: void f() { A::f();}

}

int main()

{

C c;

c.f();    // правильный вызов функции A::f()

c.B::f(); // правильный вызов функции B::f()

}

Базовые классы с одинаковым именем не могут присутствовать в определении производного класса. Например, если попытаться определить вектор, как пару точек

Class Point {int x,y;}

Class Vector : public Point, public Point {} // ошибка

то компилятор выведет сообщение об ошибке, поскольку для объекта

Vector v;

неясно, как начальную точку вектора отличить от конечной.

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

Class Point {public: int x, y;}

Class Point2 : public Point {};

Class Vector : public Point, public Point2 {};

будет верным и будет задавать вектор как пару точек. Теперь обращение к начальной и конечной точкам вектора будет производиться с помощью оператора разрешения области видимости. Для данного случая иерархическая структура классов приведена на рис. 4.2.