Объявление наследования. Порядок определения новых и переопределения унаследованных компонент класса. Модификация области видимости компонент класса при наследовании.

в С++

Определение класса, наследуемого от некоторых, уже существующих классов, производится следующим образом:

class имя_класса: список_базовых_классов

{//определение собственных компонент

//переопределение унаследованных компонент базовых классов

};

Здесь список_базовых_классов – это перечень (через запятую) тех классов, от которых будет унаследован определяемый класс. Эти классы к моменту определения производного класса должны быть определены. После подобного объявления все общедоступные и защищенные компоненты базовых классов становятся компонентами производного класса без дополнительного определения. В некоторых источниках базовый класс называют суперклассом, а производный - подчиненным классом.

Общие правила порождения классов:

1) количество базовых классов в списке порождения может быть любым;

2) один и тот же класс не может быть задан в списке порождения дважды;

3) базовый класс к моменту определения производного должен быть определен или описан;

4) ни базовый, ни порожденный класс не могут быть определены с помощью ключевого слова union;

 

Рассмотрим использование механизма наследования в С++ на конкретном примере. Определим классы А, В и С, находящиеся в отношениях наследования:


struct A

{private:

int a1;

public:

int a2;

void funcA()

};

//наследуем класс В от А

struct B:A

{private:

int b1;

public:

int b2;

void funcB()

};

 

//наследуем класс С от В

struct C:B

{Iprivate:

int c1;

public:

int c2;

void funcC()

};


В приведенном примере класс С унаследован от класса В, а тот в свою очередь унаследован от класса А. При наследовании различают прямые и косвенные базовые классы. Прямой базовый класс упоминается в списке баз производного класса. Косвенным базовым классом считается класс, который является базовым для одного из классов, упомянутых в списке прямых баз данного производного класса. Класс А является прямым базовым классом для В и косвенным базовым (непрямым базовым) для С.

 

Класс С можно разделить на 3 части – часть, косвенно унаследованную от А, часть, унаследованную от В, а также собственные компоненты класса С. Соответственно, структура класса В состоит из двух частей – унаследованной от А и собственных компонент класса.

Рассмотрим содержимое функции funcC:

void C::funcC()

{a2=0; //также возможные варианты обращения A::a2=0; B::a2=0;

//B::A::a2=0; C::a2=0

b2=0; //можно также B::b2=0; C::b2=0; Однако, нельзя A::b2=0

c2=0;// можно также C::c2=0; Однако, нельзя A::c2=0; B::c2=0

funcA(); //можно A::funcA(); B::funcA(); C::funcA()

}

Последний пример иллюстрирует способы обращения к унаследованным компонентам класса. К ним можно обращаться как к собственным компонентам класса, а можно использовать так называемое квалифицированное имя компонента, которое в общем виде записывается следующим образом:

имя_класса :: имя_компонента

Использование квалифицированного имени удобно тогда, когда в базовом и производном классах определены одноименные компоненты. Например, если бы в классе А было определено компонентное данное c2, то в функции funcC() выражение с2=5 изменяло бы значение компонента, определенного непосредственно в классе С, а для того, чтобы изменить значение унаследованного от А компонента, необходимо было бы использовать выражение A::c2=5 или B::c2=5.

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

Квалифицированное имя компонента класса может быть использовано и вне его компонентных функций. Для доступа в таком случае можно использовать выражение вида

имя_объекта . имя_класса :: имя_компонента

//Листинг 2. Пример использования квалифицированного имени компонент класса из

//его окружения:

main()

{ C c;

c.C::c2=0;

c.B::funcB();

c.A::a2=2;

c.C::funcA();

}

Поиск компонента при обращении к нему всегда идет "снизу-вверх". Для вызова с.С::funcA() транслятор сначала проверит наличие функции funcA в классе С и, если такой имеется, занесет адрес метода этого класса для вызова. Если в классе С нет метода с таким названием, будет рассмотрен прямой базовый класс для С (в нашем случае – класс В) и поиск метода продолжится в нем. Если метод с таким именем будет найден – его адрес будет помещен на место вызова, иначе – поиск будет продолжен на следующем уровне иерархии классов (в классе А для рассматриваемого примера).

Еще одно важное свойство базовых и производных классов иллюстрируется в листинге 3.

//Листинг 3. Приведение указателей производного класса к базовому

main()

{ A *pta;

C c;

pta=&c;

pta->funcA();

}

В программе определен указатель на базовый класс A и объект производного класса С. При этом присвоение указателю pta адреса объекта c не потребовало операций приведения типа. Данный пример показывает, что указатель на базовый класс может ссылаться на объекты производных классов. При этом обратное преобразование недопустимо синтаксисом языка. Когда указателю на базовый класс присвоен адрес объекта производного класса, через этот указатель можно обращаться только к той части производного класса, которая унаследована от базового. Ошибку содержит следующий фрагмент:

main()

{ A *pta;

C c;

pta=&c;

// pta->funcС(); Ошибка !!! Указатель pta адресует только

//ту часть объекта с, которая унаследована от класса А

((С*)pta)->funcC(); //правильный вызов

}

 

Правила, по которым изменяются области видимости компонент класса при наследовании, приведены в таблице 1.

Изменение области видимости компонент базового класса в производном

Область видимости в базовом классе Спецификатор доступа в списке порождения Область видимости в производном классе
производный класс объявлен через struct производный класс объявлен через class
public нет public private
public public public public
public protected protected protected
public private private private
protected нет public private
protected public protected protected
protected protected protected protected
protected private private private
private * не доступны

Следующий пример иллюстрирует приведенные в таблице правила трансформации области видимости компонент при наследовании.

// Листинг 5. Примеры изменения области видимости компонент при наследовании

class A

{ public:

int x;

};

class B

{protected:

int y;

};

class C

{public:

int z;

};

class D: public A, private B, C

{…

};

main()

{ D d;

d.x=5; //компонент х в классе D имеет область видимости

//public (2-е правило в таблице 1)

//d.y=1; Ошибка!!! компонент y в классе D имеет область

//видимости private (8-е правило в таблице 1)

//d.z=0; Ошибка!!! компонент z в классе D имеет область

//видимости private (1-е правило в таблице 1)

}