C++. Бархатный путь. Часть 2 - Базовые и производные классы

ОГЛАВЛЕНИЕ

Базовые и производные классы

Синтаксис наследования задаётся необязательным элементом заголовка класса, который называется спецификацией базы и описывается следующим множеством форм Бэкуса-Наура:

СпецификацияБазы ::= : СписокБаз
СписокБаз ::= [СписокБаз,] ОписательБазы
ОписательБазы ::= ПолноеИмяКласса
::= [virtual] [СпецификаторДоступа] ПолноеИмяКласса
::= [СпецификаторДоступа] [virtual] ПолноеИмяКласса

Нам ещё предстоит выяснить назначение элементов описателя базы, но уже очевидно, что спецификация базы представляет собой список имён классов. Поскольку производный класс наследует данные и функции базового класса, базовые классы обязательно должны быть объявлены до объявления производного класса.

Для начала рассмотрим пример объявления нескольких классов. В этом примере задаются отношения наследования между тремя классами (классы A, B, C). При этом C наследует свойства класса B, который, в свою очередь, является наследником класса A. В этом примере все члены классов объявляются со спецификатором public, к которому мы пока относимся (пока!) как к должному. В этих классах мы объявим (просто обозначим) самые простые варианты конструкторов и деструкторов. В настоящий момент нам важно исключительно их существование.

#include <iostream.h>
class A {
public:
A(){};
~A(){};
int x0;
int f0 () {return 1;};
};
class B : public A {
public:
B(){};
~B(){};
int x1;
int x2;
int xx;
int f1 () {return 100;};
int f2 () {return 200;};
};
class C : public B {
public:
C(){};
~C(){};
int x1;
int x2;
int x3;
int f1 () {return 1000;};
int f3 () {return 3000;};
};
void main () {C MyObject;}

Перед нами пример простого наследования. Каждый производный класс при объявлении наследует свойства лишь одного базового класса. В качестве базового класса можно использовать лишь полностью объявленные классы. Неполного предварительного объявления здесь недостаточно. Для наглядного представления структуры производных классов используются так называемые направленные ациклические графы. Узлы этого графа представляют классы, дуги - отношение наследования.

Вот как выглядит направленный ациклический граф ранее приведённого в качестве примера производного класса C:

A
B
C

Структуру производного класса можно также представить в виде таблицы (или схемы класса), отображающей общее устройство класса:

A
B
C

В C++ различаются непосредственные и косвенные базовые классы. Непосредственный базовый класс упоминается в списке баз производного класса. Косвенным базовым классом для производного класса считается класс, который является базовым классом для одного из классов, упомянутых в списке баз данного производного класса.

В нашем примере для класса C непосредственным базовым классом является B, косвенным - A. Следует иметь в виду, что порядок "сцепления" классов, образующих производный класс, зависит от реализации, а потому все схемы классов и объектов имеют характер имеют чисто иллюстративный характер.

Дополним нашу схему, включив в неё объявления всех членов классов, включая, конструкторы и деструкторы.

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

A
A();
~A();
int x0;
int f0 ();
B
B();
~B();
int x1;
int x2;
int xx;
int f1();
int f2();
C
C();
~C();
int x1;
int x2;
int xx;
int f1();
int f2();

Это схема класса, а не объекта. Образно говоря, наша схема подобна схеме многоэтажного бункера, разделённого на три уровня. На схеме эти уровни разделяются двойными линиями. Класс C занимает самый нижний уровень. Именно этот класс имеет неограниченные (по крайней мере, в нашей версии объявления производного класса) возможности и полномочия доступа к элементам базовых классов. Именно с нижнего уровня можно изменять все (опять же, в нашей версии объявления класса) значения данных-членов класса и вызывать все (или почти все) функции-члены класса.