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; };