Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
шпоры по Леонову, 5семестр.doc
Скачиваний:
0
Добавлен:
01.03.2025
Размер:
503.3 Кб
Скачать

9. Простое наследование. Контроль доступа к атрибутам класса.

Class D: public B { Int j;

Void f( ) {B:: f( );}

D( ): B(0) {…} }; D-базовый класс, В – производный.

Class B

B(int): {Int i;

Void f( ); }; D.d; d.i; d.f( );

Наследование – механизм создания базового класса с общими атрибутами и исп-ся его в других объектов путем ссылки на него. Производный класс кроме своих собственных членов содержит члены базового класса. Произв. класс наследует свойства базового, поэтому их отношения называют наследованием. Производный класс обычно больше базового, он содержит больше данных и ф-й. С объектом производного класса можно обращиться как с объектом базового класса при обращинии к нему при помощи указателей и ссылок. Обратное неверно. Член производного класса может пользоваться открытыми и защищенными членами базового класса так, как будто они объявлены в самом производном классе, но не может использовать закрытые имена базового класса. Сначала вызывается констркутор базового класса, а потом производного, деструктор наоборот.

Контроль доступа: член класса м. б.: 1.закрытым(private), 2.защищенным(protected) или 3.открытым(public):

1. Имя члена может использоваться только в ф-ях членах и друзьях класса, в котором он объявлен.

2. Его имя может использоваться только в ф-ях-членах и друзьях класса, в котором он объявлен, и классов, производных от него. 3. Его именем может пользоваться любая ф-я. Следовательно, существует 3 вида ф-й, имеющих доступ к классу: 1. Ф-ии, реализующие класс (члены и друзья), 2. Ф-ии, производных классов), 3. Все остальные ф-ии.

10. Виртуальные функции (вф) и абстрактные классы.

ВФ решают проблему, связанную с полем типа, предоставляя возможность объявить в базовом классе ф-ии, кот. можно заместить в каждом производном классе. Компилятор и загрузчик гарантируют правильное соответствие между объектами и ф-ми, применяемыми к ним. Некоторые классы, такие как Shape(фигура), представляют абстрактную концепцию, для которой не могут существовать объекты. Класс шейп имеет смысл только в качестве базы для производных классов. ВФ делается чисто виртуальной при помощи инициализатора =0: Сlass Shape { // абстрактный класс

Public: Virtual void rotate(int) =0; // чисто ВФ;

Virtual void draw( ) =0; //…. };

Класс с одной или несколькими чисто ВФ называется абстрактным классом. Невозможно создать объект абстрактного класса, такой класс можно использовать только как интерфейс и в качестве базы для других классов. Чисто ВФ, кот. не определена в производном классе, остается чисто виртуальное, поэтому такой производный класс, как и базовый, явл абстрактным.

11. Множественное и виртуальное наследование.Множественное наследование

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

class A

{

};

class B

{

};

class C : public A, public B

{

};

При создании объектов-представителей производного класса, порядок расположения непосредственных базовых классов в списке баз определяет очерёдность вызова конструкторов умолчания. Этот порядок влияет и на очерёдность вызова деструкторов при уничтожении этих объектов. Но эти проблемы, также как и алгоритмы выделения памяти для базовых объектов, скорее всего, относятся к вопросам реализации. Вряд ли программист должен акцентировать на этом особое внимание. Более существенным является ограничение, согласно которому одно и то же имя класса не может входить более одного раза в список баз при объявлении производного класса. Это означает, что в наборе непосредственных базовых классов, которые участвуют в формировании производного класса не должно встречаться повторяющихся элементов. Вместе с тем, один и тот же класс может участвовать в формировании нескольких (а может быть и всех) непосредственных базовых классов данного производного класса. Так что для непрямых базовых классов, участвующих в формировании производного класса не существует никаких ограничений на количество вхождений в объявление производного класса:

class A

{

public:

int x0, xA;

};

class B : public A

{

public:

int xB;

};

class C : public A

{

public:

int x0, xC;

};

class D : public B, public C

{

public:

int x0, xD;

};

В этом примере класс A дважды используется при объявлении класса D в качестве непрямого базового класса.

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

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

Такой фрагмент объекта мы будем называть производным фрагментом-представителем данного класса.

Верхние узлы графа и верхние уровни схем классов и объектов соответствуют базовым классам и фрагментам объектов, представляющих базовые и непосредственные базовые классы.

Эти фрагменты объекта мы будем называть базовыми и непосредственными базовыми фрагментами-представителями класса.

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

A A

B C

D

А вот как представляется структура производного класса в виде неполной схемы класса. Базовые классы располагаются на этой схеме в порядке, который соответствует списку базовых элементов в описании базы производного класса. Этот же порядок будет использован при изображении диаграмм объектов. И это несмотря на то обстоятельство, что порядок вызова конструкторов базовых классов определяется конкретной реализацией. За порядком вызова конструкторов базовых классов всегда можно наблюдать после определения их собственных версий.

A

B

A

C

D

А вот и схема объекта производного класса.

D MyD;

MyD ::=

A

(int)x0;

(int)xA;

B

(int)xB;

A

(int)x0;

(int)xA;

C

(int)x0;

D

(int)x0;

(int)xD;

Первое, что бросается в глаза - это множество одноимённых переменных, "разбросанных" по базовым фрагментам объекта. Да и самих базовых фрагментов здесь немало.

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

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

Например, неоднозначность содержится в следующем операторе:

MyD.xA = 100;

здесь предпринимается неудачная попытка изменения значения данного-члена базового фрагмента объекта MyD. Выражение доступа MyD.xA именует сразу две переменных xA. Разрешение неоднозначности сводится к построению такого выражения доступа, которое однозначно указывало бы функцию, объект, тип (об этом позже!) или перечислитель.

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

MyD.B::x0 = 100;

Этот оператор обеспечивает изменение значения данного-члена базового фрагмента - представителя класса B. Здесь нет никаких проблем, поскольку непосредственный базовый класс B наследует данные-члены базового класса A. Поскольку в классе B отсутствуют данные-члены с именем x0, транслятор однозначно определяет принадлежность этого элемента. Итак, доступ к данному-члену базового класса A "со стороны" непосредственного базового класса B не представляет особых проблем.

MyD.C::x0 = 100;

А теперь изменяется значение данного-члена базового фрагмента - представителя класса С. И опять же транслятор однозначно определяет местоположение изменяемой переменной. Переменная x0 была объявлена в непосредственном базовом классе C. И операция доступа указывает на эту переменную. А вот попытка изменения значения переменной x0, расположенной базовом фрагменте-представителе класса A "со стороны" непосредственного базового класса C обречена. Так, оператор

MyD.A::x0 = 777;

некорректен по причине неоднозначности соотнесения класса и его члена, поскольку непонятно, о каком базовом фрагменте-представителе класса A идёт речь. Выражения доступа с составными квалифицированными именами, как например,

MyD.C::A::x0

в контексте нашей программы также некорректны: составное квалифицированное имя предполагает вложенное объявление класса. Это свойство операции доступа уже обсуждалось ранее, в разделах, непосредственно посвящённых операциям. Вложенные объявления будут рассмотрены ниже.

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

Идея состоит в том, чтобы, объявив указатель на объект-представитель базового класса, попытаться его настроить с помощью операций явного преобразования типа на соответствующий фрагмент объекта производного класса. В результате недосягаемые с помощью операции доступа фрагменты объекта превращаются в безымянные объекты простой конфигурации. Доступ к их членам в этом случае обеспечивается обычными операциями косвенного обращения. Рассмотрим несколько строк, которые демонстрируют такую технику работы с недосягаемыми фрагментами.

A* pObjA;

B* pObjB;

C* pObjC;

D* pObjD = &MyD;

// Мы начинаем с объявления соответствующих указателей.

pObjC = (C*)&MyD;

pObjA = (A*)pObjC;

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

pObjA->x0 = 999;

// А это уже элементарно!

Очевидно, что можно обойтись без поэтапных преобразований и воспользоваться свойством коммутативности операции явного преобразования типа:

((A*)(C*)pObjD)->x0 = 5;

((A*)(B*)pObjD)->x0 = 55;

// Разным фрагментам - разные значения.