Базовый класс называется виртуальным, если его поля не дублируются при неоднократном наследовании. Виртуальный базовый класс объявляется при наследовании при определении производного класса следующим образом:
сlass имя_производного_класса:
virtual public имя_виртуального_базового_класса
{
тело производного класса;
}
Пример. На рис. 4.3 приведена иерархия производных класса четырёхугольника. Для того чтобы поля четырехугольника не наследовались более одного раза, объявим его как виртуальный базовый класс. Ромб будем задавать с помощью центра, длин диагоналей и угла поворота вокруг первой диагонали.
Ниже для иллюстрации понятия виртуального класса приведём текст программы, в которой определена иерархия классов, отраженная на рис. 4.3. Класс квадрата дважды наследует координаты четырёх углов.
#include <graphics.h>
#include <math.h>
#include <conio.h>
class four // четырехугольник
{
protected:
float x[4], y[4]; // координаты вершин
public:
four(){}
four(float *ix, float *iy) // конструктор
{
int i;
for(i=0; i<4; i++)
{
x[i] = ix[i]; y[i] = iy[i];
}
}
~four() {delete x; delete y;} //деструктор
void show(); //вывод четырёхугольника на экран
};
class rect: public virtual four // прямоугольник
{
protected:
int xleft, xright, ytop, ybottom;
public:
rect(int x1, int y1, int x2, int y2):
xleft(x1), ytop(y1), xright(x2), ybottom(y2) // конструктор
{
x[0] = x1; y[0] = y1;
x[1] = x1; y[1] = y2;
x[2] = x2; y[2] = y2;
x[3] = x2; y[3] = y1;
}
};
//класс ромба
class romb: public virtual four
{
protected:
float xc, yc, alpha, a, b;
public:
romb(float x1, float y, float ugol, float d1, float d2)
{
xc = x1; yc = y; alpha = ugol; a = d1; b = d2;
x[0] = xc + (a/2)*cos(alpha);
x[0] = yc + (a/2)*sin(alpha);
x[1] = xc — (b/2)*sin(alpha);
x[1] = yc + (b/2)*cos(alpha);
x[2] = xc — (a/2)*cos(alpha);
x[2] = yc — (a/2)*sin(alpha);
x[3] = xc + (b/2)*sin(alpha);
x[3] = yc — (b/2)*cos(alpha);
}
};
// стороны квадрата параллельны осям координат
class square : public rect, public romb
{
int xcenter, ycenter; // центр квадрата
int size; // сторона квадрата
public:
square(int x0, int y0, int s): xcenter(x0+s/2), ycenter(y0+s/2),
size(s), rect(x0 — s/2, y0 — s/2, x0 + s/2, y0 + s/2),
romb(x0, y0, 3.14159/4, s, s) {show();}
};
void four :: show() // вывод четырехугольника
{
int i;
moveto (x[3], y[3]);
for(i=0; i<4; i++)
lineto(x[i], y[i]);
};
void main()
{
int gd = DETECT, gm;
initgraph (&gd, &gm,"c:\prog\bc31\bgi");
square q(320, 240, 100);.
getch(); //ожидание нажатия любой клавиши
}
В результате работы программы на экран будет выведен квадрат, сторона которого равна 100, а центр квадрата совпадает с центром экрана.
Конструкторы и деструкторы виртуальных классов. Включение полей виртуального базового класса в производный класс осуществляется один раз, а их инициализация будет происходить в таком его производном классе, который не является его непосредственным наследником.
Во время инициализации объекта некоторого класса А конструкторы классов, наследованных виртуально, активизируются перед конструкторами всех остальных базовых классов этого класса. Происходит это следующим образом:
· если в списке инициализации конструктора класса А используется инициализатор базового класса, наследованного виртуально, то активизируется конструктор этого базового класса;
в противном случае конструктор виртуального базового класса инициализируется без параметров.
·
Деструкторы активизируются в обратном порядке.
Пример. Рассмотрим программу, работающую с иерархией классов, приведенных на рис. 4.4. Эта программа иллюстрирует порядок вызова конструкторов виртуальных классов.
Ниже приведён текст программы:
#include <stdio.h> //стандартная библиотека ввода-вывода
//классы реализованы посредством конструкторов и информационных полей
class V
{
public:
int a,b,c;
V(): c(3){};
V(int p): a(p){};
};
class A: virtual public V
{
public:
A():V(3) {a=1;}
};
class B: virtual public V
{
public:
B() {b=2;}
};
class C: public A,B
{
public:
//функция вывода
void out(){printf("a=%d b=%d c=%dn",a,b,c);}
};
int main()
{
C ob1;
ob1.out();
return 0;
}
При создании объекта ob1 класса С конструктор класса С вызовет конструктор V(), который установит С=3, затем конструкторы А() и В() объектов А и В, не имеющие параметров. Затем конструктор класса А вызовет конструктор виртуального базового класса V(3), который установит а = 3, но в теле конструктора класса А будет произведено присваивание а = 1, в результате чего а станет равен 1. Затем будет вызван конструктор класса В, который установит b = 2.
Результаты работы программы
a=1 b=2 c=3
Пример. Рассмотрим иерархию классов, приведённую на рис. 4.5.
Этот пример, как и предыдущий, иллюстрирует порядок вызова конструкторов виртуальных классов, но для более сложного случая.
Ниже приведён текст программы.
#include <stdio.h> //стандартная библиотека ввода-вывода
class V1 //первый класс
{
friend class D; //дружественный класс
friend class B; //дружественный класс
int fix1;
public:
V1(int val): fix1(val){}; //конструктор
V1(): fix1(10){};
};
class V2 //второй класс
{
friend class D; //дружественный класс
int fix2;
public:
V2(): fix2(20){}; //конструктор
V2(int p): fix2(p){};
};
//схема наследования
class B: virtual public V1, virtual public V2 {};
class C: virtual public V1, virtual public V2 {};
class D: public B,C {
public:
D(): V1(30){};
D(int p): V2(p){};
//функция вывода
void out(){printf("fix1=%d fix2=%d n",fix1,fix2);}
};
int main()
{
D ob1; D ob2(100);
ob1.out();
ob2.out();
return 0;
}
При создании объекта ob1 будут вызваны конструкторы V1(30) и V2(), которые установят fix1 = 30 и fix2 = 20. При создании объекта ob2 будет вызван конструктор V1() без параметра, а затем – конструктор V2(100). Они установят fix1 = 10 и fix2 = 100.
Результаты работы программы
fix1=30 fix2=20
fix1=10 fix2=100
Пример. Рассмотрим иерархию классов, приведённую на рис. 4.6. Сначала вызываются конструкторы V1,V2,V3, а затем – B,C,D.
Ниже приведён текст программы, иллюстрирующей вышеприведённую иерархию.
#include <stdio.h> //стандартная библиотека ввода-вывода
//построения иерархии
class V1 //класс V1
{
friend class D; //дружественный класс
int fix1;
public:
V1(int val): fix1(val){};
V1(): fix1(10){};
};
class V2 //класс V2
{
friend class D; //дружественный класс
int fix2;
public:
V2(): fix2(20){};
};
class V3 //класс V3
{
int fix3;
friend D;
public:
V3(): fix3(40){};
V3(int p): fix3(p){};
};
//схема наследования
class A: virtual public V1 {};
class B: virtual public V1 {};
class C: virtual public V2, virtual public V3 {};
class D: public B,C {
public:
D(): V1(30){};
D(int p): V3(p){};
//функция вывода
void out(){printf("fix1=%d fix2=%d fix3=%dn",fix1,fix2,fix3);}
};
int main()
{
D ob1; D ob2(100);
ob1.out();
ob2.out();
return 0;
}
Результаты работы программы
fix1=30 fix2=20 fix3=40
fix1=10 fix2=20 fix3=100