- •Введение. Принципы объектно-ориентированного программирования
- •Глава 1. Классы и объекты
- •1.1. Операция разрешения области видимости ::
- •1.2. Перечислимый тип
- •1.3. Модификатор const
- •1.4. Новый тип данных – ссылка &
- •Inline определение_функции
- •2. Определение класса. Сокрытие информации.
- •3. Объект.
- •4. Конструкторы и деструкторы
- •4.1.Назначение конструктора
- •4.2. Конструктор копирования
- •X::X(X&); // где X – имя класса
- •4.3. Деструктор
- •5. Неявный указатель this
- •6. Перегрузка операций
- •7. Примеры перегрузки некоторых операций
- •7.1. Перегрузка операции [ ]
- •7.2. Перегрузка операции ()
- •7.6. Перегрузка операции (тип)
- •8. Дружественность
- •Istream
- •10. Массивы объектов.
- •11. Функции- и классы-шаблоны
- •11.1 Функции-шаблоны (родовые функции)
- •11.2 Классы-шаблоны
- •12. Член-данные класса – объекты другого класса: агрегированные классы.
- •Глава 2. Наследование. Полиморфизм
- •1. Базовый и порожденный классы
- •2. Конструкторы порожденного класса
- •3. Стандартные преобразования при наследовании
- •4. Множественное наследование. Виртуальный базовый класс
- •4.1. Прямые базовые классы
- •4.2. Виртуальный базовый класс
- •5. Полиморфизм, раннее и позднее связывание, виртуальные функции
- •5.1 Раннее (статическое) и позднее (динамическое) связывание
- •5.2. Определение виртуальной функции
- •5.3. Чистая виртуальная функция и абстрактный класс
- •5.4. Правила определения виртуальных функций
- •5.5. Механизм позднего связывания
- •6. Библиотека fstream – работа с файлами
- •Глава 3. Библиотека стандартных шаблонов (бсш). Контейнеры
- •1. Контейнер. Структура бсш.
- •2. Контейнер Vector – динамический массив
- •Контейнер list – список
- •4. Контейнер Set – множество
- •Содержание
- •Глава 1. Классы и объекты
- •Глава 2. Наследование. Полиморфизм
- •Глава 3. Библиотека стандартных шаблонов (бсш). Контейнеры
4. Множественное наследование. Виртуальный базовый класс
4.1. Прямые базовые классы
Множественное наследование имеет место, если список порождения содержит более одного базового класса.
Например,
class C: public A, public B
{ ....};
То есть схематично это выглядит так:
Функции, определенные в классах A и B в части public, доступны объекту класса C независимо от того, в каком классе они определены.
С x(...); // действует конструктор класса A, потом B, потом C
x.Fa( );
x.Fb( );
Операция :: здесь не требуется, так как функции имеют разные имена. А если в классах A и B имелась бы функция с одинаковым именем, например, F( ), то обращение к ней с объектом класса C должно содержать операцию ::
x.A:: F( ); или x.B:: F( );
Конструктор класса C отвечает за инициализацию член-данных классов A и B, то есть должен иметь, например, такой вид:
C:: C(int x, int y, int z): A(x), B(y) {c = z;}
Рассмотренный пример – это пример множественного наследования с прямыми базовыми классами. Если же базовые классы A и B в свою очередь порождены от других базовых, то эти последние для класса C будут не прямыми базовыми (или косвенными). Приведем пример такого наследования.
4.2. Виртуальный базовый класс
В случае косвенных базовых классов может возникнуть ошибочная ситуация. Рассмотрим, в чем она заключается. Пусть, например, имеется такая иерархия порождения
Рассмотрим пристальнее, что означает такое наследование:
Эта схема показывает, что объект класса D будет содержать 2 копии член-данного int a класса A, что может привести к двусмысленности. Например, если в классе D есть функция
int D:: Geta() {return a;}
и член-данное a находится в части protected класса A, то компилятор все равно выдаст сообщение об ошибке – «двусмысленность»: так как непонятно член-данное a надо взять из части B:: A или C:: A. Избежать двусмысленности можно, указав явно класс B или C.
int D:: Geta() {return C:: a;}
Однако наличие в дереве наследования нескольких копий одного и того же базового класса не только вносит путаницу, но и приводит к лишним затратам памяти. Чтобы этого не происходило, базовый класс A в списке порождения надо объявить виртуальным:
class B: public virtual A {...};
class C: public virtual A {...};
class D: public B, public C {...};
В этом случае в объектах классов B и C будут сформированы указатели на член-данные базового класса A. То есть картина будет такой:
Э
то
представление классов соответствует
такому дереву наследования:
Из рисунка видно, что в данном случае неоднозначности для член-данного a не будет, так как a – одна ячейка!
Замечание. В рассмотренных двух способах порождения класса D:
не через виртуальный базовый класс A;
через виртуальный базовый класс A
конструктор класса D определяется по-разному. В первом случае конструктор класса D не «заботится» об инициализации член-данных класса A, то есть определяется, например, так
D(int r, int s): B(r), C(s) {...}.
А во втором случае конструктор класса D отвечает и за инициализацию член-данных виртуального базового класса A. То есть должен быть задан, например, так
D(int r, int s, int q): A(r), B(s), C(q) {...}
В заключение рассмотрим пример наследования: порождение класса Kolobok из двух базовых классов Bar и Circ из п. 12 главы 1.
const int CF = 7;
class Bar
{ protected: int h, w, x, y;
public:
Bar(int hh, int ww, int xx, int yy)
{h = hh; w = ww; x = xx; y = yy;}
void Show(int c) { }
};
class Circ
{ int r;
protected: int x, y;
public:
Circ(int rr, int xx, int yy)
{r = rr; x = xx; y = yy;}
void Show(int c) { }
};
class Kolobok: public Circ, public Bar
{ int numb; char * name; // место и имя
public:
Kolobok(int h, int w, int x, int y, int r, int m, char * s):
Bar(h, w, x, y), Circ(r, x + w / 2, y - r)
{numb = m; name = new char [strlen(s) + 1];
strcpy(name, s);
}
~Kolobok() {delete [ ] name;}
void Show(int cb, int cc, int ck)
{Bar:: Show(cb); Circ:: Show(cc);
…
}
};
void main( )
{int x = 200, dx = 100, i;
Kolobok klb[3] = {Kolobok(100, 50, x, 150, 40, 2, “два”),
Kolobok(150, 50, x = x + dx, 100, 35, 1, “один”),
Kolobok(50, 50, x = x + dx, 200, 30, 3, “три”)
};
for(i = 0; i < 3; i++)
klb[i].Show(4, 14, 12);
Kolobok *pbc = new Kolobok(50, 50, 300, 400, 50, 4);
pbc –> Show(3, 12, 14);
delete pbc;
}
