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<<" "; cout<<"Вторая дробь (числитель равен -15,знаменатель равен 10): "; cout<<r2<<" "; 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(); // правильный вызов }
Неоднозначность, возникающая при вызове функций, может быть преодолена с помощью переопределения функции в производном классе, например:
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.6. Виртуальные классы
Базовый класс называется виртуальным, если его поля не дублируются при неоднократном наследовании. Виртуальный базовый класс объявляется при наследовании при определении производного класса следующим образом:
с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=\%d ",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
Ниже приведён текст программы.
#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 ",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=\%d ",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
|
|