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

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( ) объявить виртуальной.

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