Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Пособие-ООП - копия.doc
Скачиваний:
20
Добавлен:
17.08.2019
Размер:
907.78 Кб
Скачать

Перегрузка функций.

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

Классическим примером перегрузки функций является наличие нескольких конструкторов в классе.

Перегруженные функции имеют следующие свойства:

  • должны отличаться сигнатурой, т.е. числом, порядком следования и типами параметров; тип возвращаемого значения и имена параметров не учитываются; пример перегруженной функции vvod:

void vvod(int m, int n) {...}

void vvod(float m, float n) {...}

void vvod(float m) {...}

void vvod(char ch) {...}

  • должны быть объявлены в одной области действия, например, класса, файла; элементы-функции разных классов не являются перегруженными, так как имеют свою область действия класс.

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

Алгоритм выбора функции состоит из следующих этапов:

  • проверка на точное соответствие;

  • проверка на стандартные преобразования типов;

  • проверка на преобразования, определяемые классом (см. выше - в примерах на преобразование из float в complex и из complex в float эти типы становятся совместимыми).

Примеры:

int x, y; vvod(x, y); //вызов vvod(int, int), точное соответствие

int x; vvod(x); //вызов vvod(float), стандартное преобразование

vvod((char)1); //вызов vvod(char), преобразование пользователя

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

В связанных классах (базовом и производном) наличие функций с одинаковой сигнатурой приводит к неоднозначности доступа. Программист должен сам заложить раннее связывание, т.е. для вызова необходимой функции использовать имя соответствующего класса с операцией разрешения видимости (::).

В C++ имеются средства, позволяющие переложить связывание вызова таких функций с определениями функций с программиста на саму программу во время ее выполнения. Это механизм виртуальных функций или позднее связывание.

Чистый полиморфизм

"Комбинация виртуальных функций и общего полиморфизма представляет собой форму чистого полиморфизма и является наиболее общим и гибким способом формирования программного обеспечения"[4].

Виртуальные элементы-функции

Виртуальная функция - это элемент-функция базового класса в иерархии наследования со спецификатором virtual, переопределенная в производных классах и вызываемая в зависимости от класса через указатель или ссылку на базовый класс.

Виртуальная функция имеет следующие свойства:

- определяется в базовом классе иерархии наследования со словом virtual и переопределяется в производных классах, где слово virtual не обязательно;

- имя, сигнатура и тип возвращаемого значения функций базового и производных классов должны совпадать;

- не может быть объявлена как static, так как наследуется;

- выбор соответствующего экземпляра функции производится через указатель (или ссылку) на базовый класс в зависимости от типа (класса) объекта, адресуемого указателем (или ссылкой), а не в зависимости от типа указателя (или ссылки);

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

Пусть имеется иерархия классов (рис. 2.5), в каждом из которых содержится элемент-функция void vyvod().

Рис. 2.5. Пример иерархии классов

Требуется составить программу последовательного вызова функций vyvod() классов cl1, cl2, cl3.

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

Таблица 2.3. Использование указателей и ссылок.

Объекты

Указатели cl1* p[3];

или ссылки

cl1 obj1;

cl2 obj2;

cl3 obj3;

p[0]=&obj1

p[1]=&obj2;

p[2]=&obj3;

cl1& r1=obj1;

cl1& r2=obj2;

cl1& r3=obj3;

Пример 1: программа с виртуальной функцией vyvod():

//файл заголовков virt1.hpp

#include <iostream.h>

#include <conio.h>

class cl1

{ public: virtual void vyvod(); };

class cl2:public cl1

{ public: void vyvod(); };

class cl3:public cl2

{ public: void vyvod(); };

//файл кодов virt1.cpp

#include "virt1.hpp"

void cl1::vyvod() { cout<<"Работники предприятия\n"; }

void cl2::vyvod() { cout<<"Работники цеха\n"; }

void cl3::vyvod() { cout<<"Рабочие участка\n"; }

main()

{

clrscr();

cl1 obj1; cl2 obj2; cl3 obj3;

cl1 *p[3]; //cl1& r1=obj1; ссылки

p[0]=&obj1; //cl1& r2=obj2;

p[1]=&obj2; //cl1& r3=0bj3; //Результат решения:

p[2]=&obj3; //r1.vyvod(); //Работники предприятия

for (int i=0;i<3;i++)//r2.vyvod(); //Работники цеха

p[i]->vyvod(); //r3.vyvod(); //Рабочие участка

getch(); }

Итак, вызываются последовательно экземпляры виртуальной функции vyvod() классов cl1, cl2, cl3, что и было необходимо.

Составим пример без виртуальных функций. Если просто убрать слово virtual, то три раза будет вызываться функция vyvod() класса cl1. Раннее связывание не дало результата. После инициализации (p[1]=& obj2; p[2]=&obj3;) произойдет преобразование cl2 и cl3 в cl1.

Для решения этой проблемы добавим в базовый класс cl1 элемент-данное id, значения которого идентифицируют классы. Эти значения присваиваются конструкторами классов.

Элемент-функция vyvod() класса cl1 просматривает значения id, распознает тип объекта и с помощью оператора switch вызывает функции vyvod() соответствующих классов.

Пример 2: программа с не виртуальной функцией vyvod():

//файл заголовков virt2.hpp

#include <iostream.h>

#include <conio.h>

enum ident {id_cl1, id_cl2, id_cl3};

class cl1

{ public: ident id;

void vyvod();

cl1() {id=id_cl1;}

};

class cl2: public cl1

{ public: void vyvod();

cl2() {id=id_cl2;}

};

class cl3: public cl2

{ public: void vyvod();

cl3() {id=id_cl3;}

};

//файл кодов virt2.cpp

#include "virt2.hpp"

void cl1::vyvod()

{ switch(id)

{ case id_cl1: cout<<"Работники предприятия\n"; break;

case id_cl2: ((cl2 *)this)->vyvod(); break;

case id_cl3: ((cl3 *)this)->vyvod(); break;

}

}

void cl2::vyvod() { cout<<"Работники цеха\n"; }

void cl3::vyvod() { cout<<"Рабочие участка\n"; }

//функция main() без изменений (см. пример 1)

Рассмотрим алгоритм вызова виртуальных функций или позднее связывание. Позднее (динамическое) связывание заключается в том, что связывание вызова функции с необходимым экземпляром функции осуществляется динамически во время выполнения программы. Вызов функции обрабатывается в пределах своего полиморфического кластера. Полиморфический кластер - это совокупность классов, в которых определяется и переопределяется виртуальная функция. Для каждого класса кластера создается таблица виртуальных функций (VT), содержащая адреса этих функций. Каждый объект класса получает скрытый указатель (VP) на соответствующую таблицу VT, который неявно инициализируется (с помощью конструктора) адресом таблицы VT и смещением в таблице VT адреса функции. Пример в таблице 2.4:

Таблица 2.4. Использование таблиц виртуальных функций.

Объекты

Таблицы VT

Функции

obj1:

VP=(a1, hvyvod)---

//VT_cl1:

-->a1)...(a1_vyvod)--

//функция класса cl1

-->a1_vyvod)void vyvod() {...}

obj2:

VP=(a2, hvyvod)---

//VT_cl2:

-->a2)...(a2_vyvod)--

// функция класса cl2

-->a2_vyvod)void vyvod() {...}

obj3:

VP=(a3, hvyvod)---

//VT_cl3:

-->a3)...(a3_vyvod)--

// функция класса cl3

-->a3_vyvod)void vyvod() {...}

p[0]->vyvod(); //p[0]=&obj1;

p[1]->vyvod(); //p[1]=&obj2;

p[2]->vyvod(); //p[2]=&obj3;

Алгоритм вызова функции содержит следующие этапы:

  • по адресу объекта в указателе (например, в p[2]) находится объект (obj3) и определяется указатель VP на таблицу виртуальных функций VT (VP=(a3,hvyvod)), содержащий адрес таблицы (a3) и смещение в таблице VT адреса функции (hvyvod);

  • по адресу таблицы (a3) и смещению (hvyvod) в таблице VT находится адрес виртуальной функции (a3_vyvod);

  • по адресу функции находится функция vyvod() класса cl3.