Void show(void)

Void riesquare(void)

Class square

( int xq, yq, // Координаты центра квадрата

lq; // Длина стороны квадрата

// Вспомогательная функция рисования:

{

int d = lq/2;

line(xq-d,yq-d,xq+d,yq-d) ;

line(xq-d,yq+d,xq+d,yq+d);

line(xq-d,yq-d,xq-d,yq+d);

line(xq+d,yq-d,xq+d,yq+d); )

public:

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

square(int xi,int yi,int li)

{ xq = xi; yq = yi; lq = li; }

// Изобразить квадрат на экране:

void show()

{ rissquare() ; }

// Убрать с экрана изображение квадрата:

void hide()

{ int bk, cc;

bk = getbkcolor(); // Цвет фона

cc = getcolor(); // Цвет изображения

setcolor(bk); // Сменить цвет рисования

rissquare(); // Рисуем квадрат цветом фона

setcolor(cc); // Восстановить цвет изображения

}

};

В следующей программе на основе классов circ и square создан производный класс "окружность в квадрате" с именем circsqrt:

//Р10-02.СРР - окружность в квадрате - множественное

// наследование

#include <conio.h> // Для функции getch()

#include "square.cpp" // Определение класса "квадрат"

#include "circ.cpp" // Определение класса "окружность"

// Производный класс - "окружность, вписанная в квадрат";

// Класс circsqrt наследует только методы обоих базовых

// классов. В нем нет наследуемых данных.

class circsqrt : public circ, public square

{ public:

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

circsqrt(int xi, int yi, int ri):

circ(xi,yi,ri), // Явно вызываются конструкторы

square(xi,yi,2*ri) // базовых классов

{}

// Изобразить на экране окружность в квадрате:

{ circ::show(); square::show(); }

// Убрать с экрана изображение:

void hide()

{ square::hide(); circ::hide (); }

};

void main()

{ int dr = DETECT, nod;

initgraph(&dr,&mod,"с:\\borlandc\\bgi");

circsqrt Al(100,100,60);

circsqrt F4(400,300,50); A1.3how() ; getch() ; F4.3how() ; getch() ; F4.hide() ; getch() ; Al.hide() ; getch() ; closegraph() ; }

Определения базовых классов должны предшествовать их исполь­зованию в качестве базовых. Поэтому тексты из файлов square. cpp и circ.cpp включены в начало программы, после чего описывается класс circaqrt. В производном классе circsqrt телом конструктора служит пустой оператор. Выполнение конструктора circsqrt () сво­дится к последовательному вызову конструкторов базовых классов. При этом за счет соответствующего выбора параметров центры обеих фигур (квадрата и окружности) совпадают. Кроме того, длина сторо­ны квадрата выбирается равной удвоенному радиусу окружности (параметр 2 * ri), и тем самым окружность оказывается вписанной в квадрат.

В основной программе формируются два объекта a1, F4 класса circsqrt. Они последовательно выводятся на экран дисплея (рис. 10.4) и в обратном порядке убираются с экрана.

Рис. 10.4. Последовательность изображений на экране при выполнении программы Р10-02. СРР

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

class X { . . . ; f () ; . . . }; class У: public X { ... ); class Z: public X { ... }; class D: public Y, public Z { ... );

В данном примере класс х дважды опосредованно наследуется классом D. Особенно хорошо это видно в направленном ациклическом графе (НАГ):

Проиллюстрированное дублирование класса соответствует вклю­чению в производный объект нескольких объектов базового класса. В нашем примере существуют два объекта класса х, и поэтому для устранения возможных неоднозначностей вне объектов класса D нуж­но обращаться к конкретному компоненту класса х, используя полную квалификацию: D::Y::X::f() ИЛИ D::Z::X::f(). Внутри объекта класса d обращения упрощаются: У: :х: :f о или Z: :X: :f(), но тоже содержат квалификацию.

В качестве содержательного примера с дублированием непрямого базового класса рассмотрим программу, в которой определен класс spotelli - круглое пятно, вписанное в эллипс. Класс spotelli непо­средственно базируется на классах spot и ellips, каждый из которых базируется, в свою очередь, на классе point. Таким образом, point дважды входит в spotalli в качестве непрямого базового класса, т.е. дублируется. Класс point (точка на экране) уже рассматривался. Текст его определения находится в файле point.срр (см. п. 9.4). Опре­деление производного от класса point класса spot (круглое пятно на экране) находится в файле spot.cpp (см. п. 10.1). На базе класса point можно следующим образом определить класс "эллипс":

//ELLIPS.CPP - класс "эллипс"

#ifnde£ ELLIPS

#define ELLIPS 1

#include "point.cpp" // Определение класса point

class ellips : public point

{ protected: int rx,ry; // Радиусы эллипса public:

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

ellips (int xc, int yc, int rx, int ry):

point (xc,yc)

{ this->rx = rx; this->ry = ry; }

void show() // Изобразить на экране эллипс

{ ellipse(x,y,0,360,rx,ry);

return; } // Убрать с экрана изображение эллипса:

void hide() { int cc, bk;

cc = getcolor () ;

bk = getbkcolor () ;

setcolor (bk) ;

ellipse(x,y,O,360,rx,ry);

setcolor(cc); }

};

# endif

Как уже отмечалось, определение базового класса должно предшествовать его упоминанию в списке базовых классов. Поэтому в начале текстов spot.cpp и ellips.cpp помещена препроцессорная директива включения текста определения класса point.

В классе ellips конструктор предусматривает задание четырех параметров: координаты центра (хс, ус) и радиусы вдоль осей (rх, гу). Координаты хс, ус используются в качестве параметров при вызове конструктора базового класса point. Чтобы различить компоненты rх, rу класса ellips и обозначенные теми же идентификатора-ми формальные параметры конструктора, используется указатель this. В классе ellips две общедоступные функции show() - изобразить эллипс на экране дисплея; hide() - убрать с экрана изображение эллипса.

Текст программы:

//Р10-03.СРР - круглое пятно в эллипсе - множественное

// наследование с дублированием базовых

// классов (дублируется класс point)

#include "spot.cpp"

#include "eilips.cpp"

// Производный класс - дважды косвенно наследующий

// класс point:

class spotelli: public spot, public ellips { // Вспомогательная функция:

int min(int valuel, int value2)

{ return ( (valuel < value2) (valuel : value2); }

public:

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

spotelli(int xi,int yi,int rx,int ry) : ellips(xi,yi,rx,ry), spot(xi,yi,min(rx,ry))

{}

// Вывести изображение на экран дисплея:

void show()

( spot::show();

ellips::show(); }

void hide() // Убрать изображение с экрана дисплея

{ spot: :hide() ;

ellips: :hide() ; }

};

#include <conio.h> // Для функции

getch()

void main() { int dr = DETECT, mod;

initgraph(&dr,&mod,"c:\\borlandc\\bgi"); { spotelli Al (100,100,20,80); spotelli F4(400,300,230,100); Al.ahow() ; getch() ; F4.show() ; getch() ; F4.hide() ; getch() ; Al.hide() ; }

closegraph(); }

В классе ellips, в классе spot и в классе spotelli наследуются данные х, у класса point - координаты точки на экране. В классе point они определены как защищенные (protected) и сохраняют тот же статус доступа в производных классах, где определяют координаты центров- пятна (класс spot), эллипса (класс ellips) и эллипса с пят­ном (класс spotelli).

Класс spot мы уже разбирали.

Рис. 10.5. Последовательность изображений на экране при выполнении программы Р10-03 .СРР

Конструктор класса spotelli не выполняет никаких дополни­тельных действий - последовательно вызываются конструкторы клас­са ellips и класса spot, причем центры создаваемых фигур совпадают, а в качестве радиуса пятна выбирается меньший из радиу­сов эллипса. Используемая в этом случае функция min() определена по умолчанию как встроенная (inline) собственная (private) функция класса spotelli.

Чтобы отличать одинаково обозначенные функции, унаследован­ные классом spotelli из классов spot и eliips, при вызове show о и hide () используются полные квалифицированные имена, в которых применена операция '::'.

Функция main () не содержит ничего нового. Описаны два объекта Al, F4 класса spotelli, которые последовательно изображаются на экране и "стираются" с него.

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

class X { ... f (); ... }; class Y: virtual public X { ... }; class Z: virtual public X { ... }; class D: public Y, public Z { ... };

Теперь класс d будет включать только один экземпляр х, доступ к которому равноправно имеют классы Y и z. Графически это очень на­глядно:

Иллюстрацией сказанного может служить иерархия классов в сле­дующей программе:

//Р10-04.СРР - множественное наследование с виртуальным

// базовым классом

#include <iostream.h>

class base // Первичный (основной) базовый класс

{ int jj; char cc; char w[10] ; public:

base(int j = 0, char с = '*')

{ jj = j;

cc = c; }

};

class dbase: public virtual base { double dd; public:

dbase(double d = 0.0) : base()

{ dd = d; } };

class fbase: public virtual base { float ff; public:

fbase(float f = 0.0): base() { ff = f; } };

class top: public dbase, public fbase { long tt; public:

top (long t = 0) : dbase (), fbase () { tt - t; } };

void main()

{

cout << "ЧпОсновной базовый класс: sizeof(base) = " << sizeof(base);

cout << "^Непосредственная база: sizeof(dbase) = " << sizeof(dbase);

cout << "^Непосредственная база: sizeof(fbase) = " << sizeof(fbase);

cout << "ЧпПроизводный класс: sizeof(top) = " << sizeof(top); }

Результаты выполнения программы:

Основной базовый класс: sizeof(base) = 13

Непосредственная база: sizeof (dbase) =23

Непосредственная база: sizeof(fbase) =19 Производный класс: sizeof(top) = 33

Основной базовый класс base в соответствии с размерами своих компонентов стандартных типов int и char [11] имеет размер 13 байт. Создаваемые на его основе классы dbase и fbase занимают соответ­ственно 23 и 19 байт. (В dbase входит переменная типа double, зани­мающая 8 байт, наследуется базовый класс base, для которого требуется 13 байт, и 2 байта нужны для связи в иерархии виртуальных классов.) Производный класс top включает: один экземпляр базового класса base (13 байт); данные и связи класса dbase (10 байт); данные и связи класса fbase (6 байт); компонент long tt (4 байта).

Если в той же программе убрать требование виртуальности (атрибут virtual) при наследовании base в классах dbase и fbase, то результаты будут такими:

Основной базовый класс: sizeof(base) = 13

Непосредственная база: sizeof(dbase) = 21

Непосредственная база: sizeof(fbase) = 17

Производный класс: sizeof(top) = 42

Обратите внимание, что размеры производных классов при от­сутствии виртуальных базовых равны сумме длин их компонентов и длин унаследованных базовых классов. "Накладные расходы" памяти здесь отсутствуют.

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

class X { ... } ;

class Y: virtual public X { ... }; class Z: virtual public X { ... }; class B: virtual public X { .. }; class C: virtual public X { ... }; class E: public X { ... ); class D: public X { ... }; class A: public D, public B, public Y, public Z, public C, public E { ... };

В данном примере объект класса а включает три экземпляра объ­ектов класса х: один виртуальный, совместно используемый классами в, Y, с, z, и два невиртуальных относящихся соответственно к классам D и е. Таким образом, можно констатировать, что виртуальность класса в иерархии производных классов является не свойством класса как такового, а результатом особенностей процедуры наследования.

Возможны и другие комбинации виртуальных и невиртуальных базовых классов. Например:

class ВВ { ... };

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

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

class DD: public AA, public CC, public virtual BB { ... };

Соответствующий НАГ имеет вид:

При использовании наследования и множественного наследования могут возникать неоднозначности при доступе к одноименным ком­понентам разных базовых классов. Простейший и самый надежный способ устранения неоднозначностей - использование квалифициро­ванных имен компонентов. Как обычно, для квалификации имени компонента используется имя класса. Следующий пример иллюстри­рует упомянутую неоднозначность и ее разрешение с помощью ква­лифицированных имен компонентов:

class X { public: int d; ... }; class Y { public: int d; ... }; class Z: public X, public Y, { public:

int d;

d - X::d + Y: :d; };