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

Тема 9. Виртуальные функции (2часа)

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

Приведенный пример в определенной мере отражает смысл применения раннего и позднего связывания, соответственно. Очевидно, что для данного примера первый вариант оптимален. Во втором случае мы слишком много всего предусмотрели, но нам это не понадобилось. С другой стороны, если по дороге на рынок мы решим, что апельсины нам не нужны и решим купить 10 кг. яблок, то в первом случае мы уже не сможем этого сделать. Во втором же случае - легко.

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

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

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

Очевидно, что скорость и эффективность при раннем связывании выше, чем при использовании позднего связывания. В то же время, позднее связывание обеспечивает некоторую универсальность связывания.

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

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

9.1. Определение виртуальных методов

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

Адрес виртуального метода известен только в момент выполнения программы. Когда происходит вызов виртуального метода, его адрес берется из таблицы виртуальных методов своего класса. Таким образом, вызывается то, что нужно.

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

Для иллюстрации применения виртуальных методов приведу пример на языке С++, который был позаимствован из одногоC++ Tutorial.

#include <iostream.h> // подключение стандартной библиотек С++, в

// которой описаны некоторые функции, применяемые в программе

class vehicle // класс "транспортное средство"

{

int wheels;

float weight;

public: // начало публичного(открытого) раздела класса

virtual void message(void) {cout << "Транспортное средство\n";}

// описание виртуальной функции message класса vehicle и реализация этой

// функции. При вызове функции message класса vehicle на экран монитора

// будет выведена строка "Транспортное средство"

};

class car : public vehicle // класс "легковая машина", унаследованный из

// класса "транспортное средство"

{

int passenger_load;

public: //

void message(void) {cout << "Легковая машина\n";}

// описание виртуальной функции message класса car и реализация этой

// функции. При вызове функции message класса car на экран монитора

// будет выведена строка " Легковая машина "

};

class truck : public vehicle // класс "грузовая машина", унаследованный из

// класса "транспортное средство"

{

int passenger_load;

float payload;

public:

int passengers(void) {return passenger_load;}

};

class boat : public vehicle // класс "лодка", унаследованный из

// класса "транспортное средство"

{

int passenger_load;

public:

int passengers(void) {return passenger_load;}

void message(void) {cout << "Лодка\n";}

// описание виртуальной функции message класса boat и реализация этой

// функции. При вызове функции message класса boat на экран монитора

// будет выведена строка "Лодка"

};

void main() // основной исполняемый блок программы

{

vehicle *unicycle; // описываем переменную unicycle как указатель на

// объект класса vehicle

unicycle = new vehicle; // Создаем объект класса vehicle,

// указатель unicycle указывает на этот объект

unicycle->message(); // вызываем метод message объекта

delete unicycle; // удаляем объект unicycle

// Все последующие блоки по 3 строки абсолютно идентичны первому

// блоку с той лишь разницей, что изменяется класс создаваемого объекта

// на car, truck, boat

unicycle = new car;

unicycle->message();

delete unicycle;

unicycle = new truck;

unicycle->message();

delete unicycle;

unicycle = new boat;

unicycle->message();

delete unicycle;

}

Результаты работы программы (вывод на экран):

Транспортное средство

Легковая машина

Транспортное средство

Лодка

Рассмотрим приведенный пример. У нас есть три класса car,truckиboat, которые являются производными от базового классаvehicle. В базовом классеvehicleописана виртуальная функция message. В двух из трех классов(car, boat) также описаны свои функцииmessage, а в классеtruckнет описания своей функцииmessage. Все строки без комментариев не имеют принципиального для данного примера значения. Теперь пробежимся по основному блоку программы - функцииmain(). Описываем переменнуюunicycle, как указатель на объект типа vehicle. В данном случае воспринимайте работу с указателем, как с самим объектом (указатель указывает на адрес объекта в памяти). Затем, создаем объект классаvehicle, переменная unicycle указывает на этот объект. После этого вызываем методmessageобъектаunicycle, а в следующей строке удаляем этот объект. В следующих трех блоках по 3 строки проводим аналогичные операции, с той лишь разницей, что работаем с объектами классовcar, truck, boat. Применение указателя позволяет нам использовать один и этот же указатель для всех производных классов. Нас интересует вызов функцииmessageдля каждого из объектов. Если бы мы не указали, что функцияmessageклассаvehicleявляется виртуальной(virtual), то компилятор статически (жестко) связал бы любой вызов метода объекта указателяunicycleс методомmessageклассаvehicle, т.к. при описании мы сказали, что переменнаяunicycleуказывает на объект классаvehicle. Т.е. произвели бы раннее связывание. Результатом работы такой программы был бы вывод четырех строк "Транспортное средство". Но за счет применения виртуальной функции в классе мы получили несколько другие результаты.

При работе с объектами классов carиboatвызываются их собственные методыmessage, что и подтверждается выводом на экран соответствующих сообщений. У классаtruckнет своего методаmessage, по этой причине производится вызов соответствующего метода базового классаvehicle.

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

Выводы:

Виртуальные методы описываются с помощью ключевого слова virtualв базовом классе. Это означает, что в производном классе этот метод может быть замещен методом, более подходящим для этого производного класса. Объявленный виртуальным в базовом классе, метод останется виртуальным для всех производных классов. Если в производном классе виртуальный метод не будет переопределен, то при вызове будет найден метод с таким именем вверх по иерархии классов (т.е. в базовом классе).

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

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