
- •Введение. Принципы объектно-ориентированного программирования
- •Глава 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. Библиотека стандартных шаблонов (бсш). Контейнеры
5. Полиморфизм, раннее и позднее связывание, виртуальные функции
« В С++ одним из наиболее туманных понятий, с точки зрения новичков, является полиморфизм. Само слово полиморфизм достаточно экзотично и, видимо, по этой причине оно прижилось, хотя есть и другие, может быть, более подходящие термины. Программисты – странный народ. Они часто стремятся сделать из мухи слона просто так, ради прикола. Существуют даже ежегодные состязания среди программистов по С, где первый приз получает тот, кто напишет самую труднопонимаемую программу. Программисты черпают информацию из книг, статей в журналах, материалов конференций и т.п. Чем больше людей интересуются языком, тем лучше. Странно звучащие термины скорее вызовут любопытство слушателя или читателя, чем обыденная речь. А любопытство, в свою очередь, стимулирует изучать новые свойства языка. Чем больше людей изучают язык, тем он популярнее. Именно так и случилось с С++ и термин полиморфизм привлек внимание не одного любопытного читателя»
Тэд Фейсон.
Полиморфизм – от греческих слов poly (много), morphos (форм) – множественность форм. Практически с понятием полиморфизма мы уже сталкивались – это перегрузка функций и операций. При наследовании оно тоже используется через понятие виртуальных функций.
5.1 Раннее (статическое) и позднее (динамическое) связывание
Вспомним пример классов A и B из п.1.
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( );
};
Рассмотрим вариант, когда функции вывода член-данных в этих классах имеют одинаковое имя Print( ):
void A:: Print( ) {cout << d1 << ’ ‘ << d2 << ’ ‘ << d3 << ’ ‘;}
void B:: Print( ) {A:: Print( ); cout << d3 << d4;}
Функции Print( ) нарушают условия перегрузки для обычных функций: у них не только одинаковое имя, но и список аргументов. Однако это не приведет к сообщению об ошибке компилятором, так как, например, в таком фрагменте:
A x;
B y;
x.Print( ); // Print( ) класса A
y.Print( ); // Print( ) класса B
выбор функции компилятор определяет по типу объектов: x или y. Причем этот выбор осуществляется еще на этапе компиляции. Такое связывание вызова функции с конкретной функцией на этапе компиляции и называется ранним или статическим связыванием.
Используя свойство преобразования объектов, ссылок и указателей порожденных классов к базовому, программы можно делать более гибкими. Например, в некотором месте программы может вызываться функция Print( ) из любого класса, причем компилятор на этапе трансляции не может определить адрес вызываемой функции, но он может задать алгоритм вычисления этого адреса при выполнении программы.
Например, пусть еще определен класс C:
class C: public A
{int d5;
public:
C( ) {d5 = 100;}
C(int a, int b, int c, int d): A(a, b, c), d5(d) { }
void Print( ) {A:: Print(); cout << ’ ‘ << d5;}
...
};
и программа:
void main( )
{A x; B y(1, 2, 3, 4, 5, 6); C z; A * pa;
int k; cin >> k;
switch (k)
{case 0: pa = &x; break;
case 1: pa = &y; break;
case 2: pa = &z; break;
default:: cout << »СТОП!»; return;
}
pa –> Print( );
...
}
В этом примере до исполнения программы неизвестно, адрес какого объекта будет занесен в указатель pa и, следовательно, функция Print( ) из какого класса – A, B или C будет вызвана. Другими словами, связать вызов функции Print( ) с конкретным экземпляром этой функции возможно только при выполнении программы по специальному алгоритму (см. п.5.5). Это так называемое позднее или динамическое связывание. Но в приведенном выше примере будет пока вызываться функция A::Print( ) и выводить базовую часть любого объекта. Чтобы происходил действительно вызов разных функций в зависимости от значения указателя pa, надо функцию Print( ) объявить виртуальной.