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

Глава 2. Наследование. Полиморфизм

1. Базовый и порожденный классы

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

Итак, пусть определен класс A.

class A

{ private: int d1, d2, d3;

public:

A( ) {d1 = d2 = d3 = 0;} // конструктор по умолчанию

A(int x, int y, int z) {d1 = x; d2 = y; d3 = z;} // конструктор с аргументами

void PrintA( ) {cout << d1 << ’ ‘ << d2 << ’ ‘ << d3;}

};

Определим новый класс B

тип порождения

class B: public A[,private A1,...] // класс B порожден из класса A

список порождения

{ int d3, d4;

public:

B( ); // конструктор без аргументов

B(int, int, int); // конструктор с аргументами

int Sum3( );

void PrintB( );

};

Класс A – базовый, класс B – порожденный. Говорят, что класс B наследует все член-данные и член-функции класса A независимо от того, в какой части базового класса они находятся. Таким образом, класс B – это расширенный класс A.

+ все член-функции классов A и B.

Но это наследование имеет особенность такого рода: член-функции класса B не имеют прямого доступа к член-данным из части private базового класса. При попытке задать такую функцию в классе B

void B:: PrintB( ) {cout << d1 << ’ ‘ << d2......}

компилятор выдаст сообщение о нарушении принципа инкапсуляции.

В данном случае наследование понимается как возможность доступа член-функциями класса B к приватным данным базового класса A косвенно, то есть через член-функции класса A из части public. Таким образом, вывести член-данные из приватной части класса A можно, используя функцию PrintA( ) класса A:

void B:: PrintB( ) {PrintA( ); cout << ’ ‘ << d3 << ’ ‘ << d4;}

Функции вывода в классах A и B могут иметь и одинаковые имена:

void A:: Print( ) {cout << d1 << ’ ‘ << d2 << ’ ‘ << d3;}

void B:: Print( ) {A:: Print( ); cout << ’ ‘ << d3 << ’ ‘ << d4;}

Обратите внимание, что, не приписав A:: перед вызовом функции Print( ) класса A, мы получили бы рекурсивное обращение к функции Print( ) класса B, что привело бы к бесконечному обращению, так как выхода из рекурсии нет.

Однако не всегда удобно использовать приватные данные базового класса в порожденном таким косвенным способом, то есть через его член-функции. Можно разрешить член-функциям порожденного класса использовать член-данные базового класса непосредственно. Таким данным присваивается тип доступа protected (зашищенный).

class A

{ int d1;

protected : int d2, d3;

public:

...

int Get1( ) {return d1;}

...

};

В этом случае член-функциям класса B (только им и наследникам класса B) разрешен прямой доступ к член-данным из части protected класса A. Например, в классе B можно определить такую функцию

int B:: FF( ) {return d2 * d4;}

Операция :: очень широко используется при наследовании: не только член-функции могут иметь одинаковые имена, но и член-данные (в нашем примере d3), и тогда конфликт неоднозначности разрешается этой операцией.

Например,

int B:: Sum3( ) {return A:: d3 + d3;}

А член-функция вывода в классе B может быть определена такой функцией

void B:: Print() {cout << Get1() << ’ ‘ << d2 << ’ ‘ << A:: d3 << ’ ‘ << d3 << ’ ‘ << d4;}

Перечислим типы доступа для объявленных классов:

а) член-функции класса A имеют доступ ко всем собственным член-данным d1, d2, d3;

б) объекты класса A используют член-функции и член-данные только из части public (но не из protected!);

в) член-функции класа B используют собственные член-данные d3, d4 + d2, d3 из части protected класса A и всё из части public классов A и B;

г) объекты класса B имеют доступ к член-данным и к член-функциям только из части public классов A и B.

Например,

void main( )

{ A x; B y;

x.d1 = 3; // Ошибка: d1 – из части private класса A

x.d2 = 5; // Ошибка: protected – это не public!

x.Print( ); // Это верно: функция Print() из части public класса A

int k = x.Get1( ); // Верно: Get1( ) из части public A: k = x.d1

int j = y.Get1( ); // Верно: Get1( ) берет d1 из базовой части объекта y

y.Print( ); // функция B::Print( ) выведет член-данные базовой и порожденной части объекта y

y.d2 = 7; // Ошибка: член-данное d2 сохраняет тот же тип доступа protected в порожденном классе и

// функции main( ) недоступно

}

Замечание. Тип доступа protected является приватным для любых внешних функций! (в частности для main( ))

Вернемся к списку порождения. Чем отличаются порождения

class B: public A {...};

class B: protected A {...};

class B: private A {...};

public – это способ наследования, при котором типы доступа к член-данным и член-функциям базового класса в порожденном сохраняются,

protected – понижает тип доступа public в базовом до protected в порожденном,

private – это способ наследования, при котором типы доступа public и protected базового класса становятся private в порожденном (и могут использоваться только в нем, далее нет). Это делается в частных случаях, чтобы скрыть некоторые классы от дальнейших порождений.

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

Рассмотрим простое наследование. Пусть в каждом классе есть функция с одинаковым именем F( ) в части public. Имеется определение D d; и вызов d.F( ); Из какого класса будет работать функция F( )? Действует правило доминирования: одинаковое имя в порожденном классе подавляет имя в базовом. То есть в нашем примере будет работать функция D::F( ). Таким образом, дерево наследования при поиске функции просматривается снизу вверх и находится ближайшая к классу D. Но вызвать функцию F( ) можно из любого класса, используя вновь операцию ::.

Например,

d.B:: F( ); // функция F( ) из класса B

или

D *pd = new D;

pd –> C:: F( ); // вызывается функция F( ) из класса C для объекта

// класса D, определенного в динамической области.

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

  1. конструкторы;

  2. деструкторы;

  3. операции new и ‘=’, перегруженные в базовом классе;

  4. отношения дружественности.

(Тэд Фейсон: «Это как в реальной жизни – друзья ваших родителей не обязательно ваши друзья и наоборот».) Т.е. отношения дружественности не передаются ни вверх, ни вниз по дереву наследования.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]