Лекция №

Вид материалаЛекция
2.2Наследование. Лекция №8
Производный класс
Простое наследование
Множественное наследование. Лекция №9
Виртуальные базовые классы
Подобный материал:
1   2   3   4   5   6   7   8   9   10   11

2.2Наследование. Лекция №8



Наследование - это порождение производных классов от базовых классов, причем производный класс наследует элементы базовых классов и имеет собственные элементы.

Производный класс


Синтаксис определения производного класса:

class cl3: atr1 cl1[, atr2 cl2]//cl1 - имя базового класса

{ //atr1 - атрибут наследования (private, public, protected)

... }

Атрибут наследования осуществляет управление доступом к элементам базового класса (наследуемым элементам) внутри производного класса и влияет на дальнейшее наследование. По умолчанию атрибут наследования - private.

Схема управления доступом в производном классе к наследуемым элементам показана в таблице 2.1.


Таблица 2.1. Изменение атрибутов доступа при наследовании.

Атрибут

наследования

Атрибут доступа

в базовом классе

Прямой доступ в производном классе к

наследуемым элементам (по имени элем.)

private

private

protected

public

private, доступа нет варианты: 1

private, но доступ есть 2

private, но доступ есть 2

protected

private

protected

public

private, доступа нет 1

protected, доступ есть 3

protected, доступ есть 3

public

private

protected

public

private, доступа нет 1

protected, доступ есть 4

public, доступ есть 4

Как видно из таблицы, возможны следующие варианты доступа:
  • закрытые (private) элементы базового класса, независимо от атрибута наследования, становятся закрытыми (private) в производном классе, и к ним нет доступа в производном классе;
  • открытые (public) и защищенные (protected) элементы базового класса при наследовании private становятся закрытыми (private) в производном классе, но доступ к ним в производном классе есть; они становятся закрытыми от дальнейшего наследования;
  • открытые (public) и защищенные (protected) элементы базового класса при наследовании protected становятся эащищенными (protected) в производном классе;
  • открытые (public) и защищенные (protected) элементы базового класса при наследовании public становятся соответственно открытыми (public) и защищенными (protected) в производном классе.

Схема управления доступом на условном примере (см. таблицу 2.2).

Таблица 2.2. Пример осуществления доступа к элементам базового класса.

class cl1 //баз. класс

{

private: int a1; void x1();

protected: int b1;void y1();

public: int c1; void z1();

};

class cl2:public cl1//пр.кл

{

private: int a2; void x2();

protected: int b2;void y2();

public: int c2; void z2();

};

class cl3:private cl1//пр.кл

{

private: int a3; void x3();

protected: int b3;void y3();

public: int c3; void z3();

};

Прямой доступ в cl1:

a1, x1(), b1, y1(), c1, z1()

Прямой доступ в cl2:

b1, y1(), c1, z1(),

a2, x2(), b2, y2(), c2, z2()

Прямой доступ в cl3:

b1, y1(), c1, z1(),

a3, x3(), b3, y3(), c3, z3()

Доступ из внеш.функций

через объект (cl1 obj1):

c1, z1()

Доступ из внеш.функций

через объект (cl2 obj2):

c1, z1(), c2, z2()

Доступ из внеш.функций

через объект (cl3 obj3):

c3, z3()

Определение каждого класса размещают в отдельном файле заголовков. Тогда в файл заголовков производного класса директивой #include необходимо включить файл заголовков базового класса.

Простое наследование


Простым наследованием называется наследование, когда производный класс имеет непосредственно только один базовый класс.

Примеры простого наследования - схемы иерархии классов:



Рис. 2.1. Пример иерархических структур классов

Рассмотрим схему г). Пусть объявлен объект производного класса cl2: cl2 obj2;. тогда он будет иметь две части в памяти:
  • элементы, унаследованные от cl1 (явное наследование);
  • собственные элементы класса cl2.

Аналогично объект класса cl5 будет иметь две части.

Пусть объявлен объект производного класса cl3: cl3 obj3;. Он будет уже иметь три части, т.е.:
  • элементы, унаследованные от cl1 (неявное наследование);
  • элементы, унаследованные от cl2 (явное наследование);
  • собственные элементы класса cl3.

Аналогично объекты классов cl4 и cl6 будут иметь три части. Возникает вопрос о доступе внутри производного класса к элементам этих частей, т.е. к собственным и наследуемым элементам. Доступ к собственным элементам осуществляется прямо по имени элемента. Хотя производный класс наследует все элементы базового класса, возможность доступа к наследуемым элементам управляется атрибутом наследования (см. схему выше). Если доступ возможен, то он производится прямо по имени элемента базового класса.

Неоднозначность доступа возникает, если в производном классе и базовом классе имеются элементы с одинаковыми именами. Тогда для доступа к наследуемому элементу необходимо использовать имя базового класса с операцией разрешения видимости.

Пример:

class cl1 class cl2: public cl1

{ {

public: int tabn;

int tabn; void f()

... {

}; tabn=10; //доступ к собств. элементу

cl1::tabn=20;//доступ к наслед. элементу

};

Конструктор производного класса выполняет следующее:
  • инициализирует собственные элементы;
  • инициализирует наследуемые элементы.

Производный класс может явно инициализировать наследуемые элементы только непосредственного базового класса. Для этого в списке инициализации конструктора производного класса должен быть явно указан прототип конструктора непосредственного базового класса. Таким образом, конструктор производного класса может содержать параметры для самого конструктора производного класса, так и параметры для конструктора базового класса.

Пример:

class index class akt:public index

{ {

protected: protected: char tabn[10]; char* imf;

int max; char tabn[10];

public: char* imf;

index(int mk):max(mk) public:

{} akt(char* imf,int m):imf(im),index(m)

... {tabn[0]=’\0’; }

}; };

Порядок выполнения конструкторов при наследовании следующий: сначала выполняются конструкторы базового класса, затем производного класса.

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

Множественное наследование. Лекция №9


Множественным наследованием называется наследование, когда производный класс имеет непосредственно более одного базового класса. Примером множественного наследования является схема, представленная на рис. 2.2.



Рис. 2.2. Схема множественного наследования

Пусть объявлен объект производного класса: cl3 obj3;. Он будет иметь в памяти три части:
  • элементы, унаследованные от cl1 (явное наследование);
  • элементы, унаследованные от cl2 (явное наследование);
  • собственные элементы класса cl3.

Доступ к элементам производного класса (собственным и наследуемым) аналогичен доступу при простом наследовании.

Неоднозначность доступа возникает в следующих случаях:
  • в производном классе и в одном из базовых классов имеются элементы с одинаковыми именами;
  • в базовых классах имеются элементы с одинаковыми именами.

Например, имеется элемент int tabn в классах cl1 и cl2.

Для разрешения неоднозначности, как и при простом наследовании, нужно использовать имя базового класса с операцией разрешения видимости (например, obj3.cl1::tabn=20;доступ к tabn в cl1).

Конструктор производного класса позволяет инициализировать наследуемые элементы базовых классов и собственные элементы, как и при простом наследовании. Тогда в списке инициализации конструктора производного класса должны быть явно указаны имена конструкторов базовых классов. Например, конструктор класса cl3 может иметь вид: cl3(): cl1(),cl2() {...} Порядок выполнения конструкторов следующий: сначала будут выполняться конструкторы базовых классов в порядке их задания, затем - производного класса.

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

При множественном наследовании базовый класс может быть объявлен в определении производного класса только один раз, однако он может наследоваться неявно более одного раза.

Рассмотрим следующую схему иерархии классов (рис. 2.3)



Рис. 2.3. Пример множественного неявного наследования

Объект obj4 класса cl4 имеет в памяти следующие части:
  • элементы, унаследованные неявно от cl1 по левой ветви;
  • элементы, унаследованные явно от cl2;
  • элементы, унаследованные неявно от cl1 по правой ветви;
  • элементы, унаследованные явно от cl3;
  • собственные элементы класса cl4.

Как видно, объект класса cl4 содержит две части (копии) элементов класса cl1, унаследованных неявно по левой и по правой ветвям. Возникает неопределенность при доступе к наследуемым элементам класса cl1 внутри класса cl4 или через объект класса cl4. Например, доступ к элементу tabn класса cl1 с помощью выражения obj4.tabn не верен. Для устранения необходимо указать путь (ветвь) наследования, т.е. имя базового класса соответствующей ветви с операцией разрешения видимости. Например, для доступа к tabn, унаследованному по левой ветви, необходимо выражение obj4.cl2::tabn, аналогично по правой ветви - obj4.cl3::tabn.

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

Базовый класс может быть объявлен непосредственно в определении производного класса только1 раз. Нельзя объявлять явно cl1 в cl4, так как cl1 уже объявлен в cl4 неявно через cl2 и cl3.

Для устранения неоднозначности можно заменить явное наследование класса cl1 на неявное, добавив пустой (без элементов) класс cl5 между cl1 и cl4 (рис. 2.4)



Рис. 2.4. Пример устранения неоднозначности при наследовании

Тогда доступ к трем унаследованным частям класса cl1, например, к tabn с помощью объекта класса cl4 можно представить, как:

obj.cl2::tabn=10;//доступ к части, унаследованной через cl2

obj.cl3::tabn=20;//доступ к части, унаследованной через cl3

obj.cl5::tabn=30;//доступ к части, унаследованной через cl5

В некоторых задачах наличие нескольких копий базового класса в производном классе необходимо. Но если достаточно одной копии базового класса cl1 в классе cl4, то проявляются недостатки неоднозначности множественного наследования:
  • тратится дополнительная память на лишние копии класса cl1;
  • сохраняется неопределенность доступа к элементам класса cl1.

Для устранения этих недостатков имеется механизм виртуальных базовых классов.

Виртуальные базовые классы


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

Виртуальный базовый класс имеет следующие свойства:
  • объявляется добавлением ключевого слова virtual к атрибуту наследования базовых классов, производным от общего виртуального базового класса; Пример для предыдущей схемы:



class cl1 {...}//виртуальный базовый класс

class cl2:virtual public cl1 {...}

class cl3:virtual public cl1 {...}

class cl4:public cl2,public cl3 {...}
  • наследуется только одна копия виртуального базового класса в классах, порожденных от производных классов с общим виртуальным базовым классом; точнее, имеются скрытые указатели на одну унаследованную часть общего виртуального базового класса, например, объект obj4 класса cl4 имеет в памяти следующие части:

указатель-----------------------------------------> элементы,

элементы, унаследованные явно от cl2; унаследованные

указатель------------------------------------------> от cl1

элементы, унаследованные явно от cl3;

собственные элементы класса cl4;

- сохраняются все правила доступа к наследуемым и собственным элементам производного класса, но устранена неоднозначность при доступе к унаследованным элементам виртуального базового класса (доступ прямо по имени элемента), т.к. имеется только одна копия виртуального базового класса; например, доступ к элементу tabn класса cl1 возможен с помощью выражения obj4.tabn;

- устранена неоднозначность доступа при совпадении имен элементов в виртуальном базовом классе и производных от него, т.к. действует приоритет самого дальнего имени от вершины иерархии; например, при наличии элемента-функции vvod() в классах cl1 и cl2 оператор obj4.vvod() вызовет элемент-функцию vvod() класса cl2;

- конструктор виртуального базового класса вызывается не конструктором ближайшего производного класса, как обычно, а конструктором последнего производного класса в иерархии классов, в списке инициализации которого задается прототип конструктора виртуального базового класса; порядок выполнения конструкторов изменяется: сначала выполняется конструктор виртуального базового класса, затем конструкторы не виртуальных базовых классов, затем конструктор производного класса; например, конструктор класса cl4 может иметь вид: cl4(): cl1(),cl2(),cl3() {...}