- •Введение. Принципы объектно-ориентированного программирования
- •Глава 1. Классы и объекты
- •1.1. Операция разрешения области видимости ::
- •1.2. Перечислимый тип
- •1.3. Модификатор const
- •1.4. Новый тип данных – ссылка &
- •Inline определение_функции
- •2. Определение класса. Сокрытие информации.
- •3. Объект.
- •4. Конструкторы и деструкторы
- •4.1.Назначение конструктора
- •4.2. Конструктор копирования
- •X::X(X&); // где X – имя класса
- •4.3. Деструктор
- •5. Неявный указатель this
- •6. Перегрузка операций
- •7. Примеры перегрузки некоторых операций
- •7.1. Перегрузка операции [ ]
- •7.2. Перегрузка операции ()
- •7.6. Перегрузка операции (тип)
- •8. Дружественность
- •Istream
- •10. Массивы объектов.
- •11. Функции- и классы-шаблоны
- •11.1 Функции-шаблоны (родовые функции)
- •11.2 Классы-шаблоны
- •12. Член-данные класса – объекты другого класса: агрегированные классы.
- •Глава 2. Наследование. Полиморфизм
- •1. Базовый и порожденный классы
- •2. Конструкторы порожденного класса
- •3. Стандартные преобразования при наследовании
- •4. Множественное наследование. Виртуальный базовый класс
- •4.1. Прямые базовые классы
- •4.2. Виртуальный базовый класс
- •5. Полиморфизм, раннее и позднее связывание, виртуальные функции
- •5.1 Раннее (статическое) и позднее (динамическое) связывание
- •5.2. Определение виртуальной функции
- •5.3. Чистая виртуальная функция и абстрактный класс
- •5.4. Правила определения виртуальных функций
- •5.5. Механизм позднего связывания
- •6. Библиотека fstream – работа с файлами
- •Глава 3. Библиотека стандартных шаблонов (бсш). Контейнеры
- •1. Контейнер. Структура бсш.
- •2. Контейнер Vector – динамический массив
- •Контейнер list – список
- •4. Контейнер Set – множество
- •Содержание
- •Глава 1. Классы и объекты
- •Глава 2. Наследование. Полиморфизм
- •Глава 3. Библиотека стандартных шаблонов (бсш). Контейнеры
Глава 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, определенного в динамической области.
Замечание. Наследование порожденным классом свойств базового имеет следующие ограничения – наследованию не подлежат:
конструкторы;
деструкторы;
операции new и ‘=’, перегруженные в базовом классе;
отношения дружественности.
(Тэд Фейсон: «Это как в реальной жизни – друзья ваших родителей не обязательно ваши друзья и наоборот».) Т.е. отношения дружественности не передаются ни вверх, ни вниз по дереву наследования.
