129.16K
Категория: ПрограммированиеПрограммирование

Виртуальные функции. Язык С++. (Лекция 10)

1.

Лекция 10
Виртуальные функции

2.

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

3.

Пример: иерархия в животном мире
Предположим, что метод move(…) (двигаться) объявлен в
базовом классе animal. Тогда в производных классах этот
метод может быть переопределен, так как разные виды
животных могут двигаться по-разному (птицы – летать, рыбы
– плавать, и т.д.).

4.

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

5.

Виртуальные функции
В языке С++ полиморфизм включения реализуется
через механизм виртуальных функций.
Виртуальной функцией называется метод класса,
объявленный с использованием ключевого слова
virtual.
class имя_класса
{
...
virtual метод_класса(аргументы);
...
}

6.

Применение виртуальных функций:
коллекции родственных объектов
1) Пусть имеется иерархия, включающая в себя
несколько классов с одним общим предком.
Пример: геометрические фигуры на плоскости
предок –
абстрактная фигура
graph
triangle
circle
square
наследники –
конкретные формы

7.

2) Пусть требуется создать коллекцию, содержащую
родственные объекты (с общим предком).
Пример: массив геометрических фигур
элемент 0:
элемент 1:
элемент 2:
элемент 3:
элемент 4:

8.

В С++ такая коллекция может быть реализована как
массив указателей на объекты базового типа.
Пример: статический массив указателей на
объекты класса graph
graph* collection[10];
// 10 указателей
В такой массив могут быть помещены не только указатели
на объекты базового класса (graph), но и указатели на
объекты любого производного класса (circle, rectangle,
triangle и т.д.).
Это свойство иногда называют совместимостью
родственных типов.

9.

Пример размещения в массиве объектов родственных классов
graph* collection[10];
graph G(...);
circle C(...);
rectangle R(...);
triangle T(...);
collection[0]
collection[1]
collection[2]
collection[3]
=
=
=
=
&T;
&G;
&R;
&C;

10.

Проблема
Этот способ размещения родственных объектов в массиве
имеет существенный недостаток: объекты в массиве
теряют отличительные черты, так как становятся
неотличимы от предка.
Вместо метода производного класса при вызове будет
активирован метод базового класса.
collection[0]->draw();
collection[1]->draw();
...
В обоих случаях будет активирована функция draw класса
graph (вместо классов rectangle и triangle, соответственно).

11.

Для решения этой проблемы в языке С++ используют
специальный инструмент – виртуальную функцию.
РЕШЕНИЕ: Все методы, которые могут быть перегружены
в производных классах, в базовом классе объявляются как
виртуальные (virtual).
...
virtual draw();
virtual resize(...);
...
Тогда при вызове перегруженного метода (например, draw)
будет активирована функция производного класса (для
rectangle – прорисовка прямоугольника, для triangle –
треугольника и т.д.).

12.

Архитектура приложения

13.

Абстрактные классы
В рассмотренном нами примере класс graph выступает в
качестве предка для классов circle, square, triangle.
Однако в основном модуле объекты этого класса никогда
создаваться и использоваться не будут.
Базовый класс, объекты которого в программе не
реализуются, называется абстрактным классом. Его
назначение – быть родителем для одного или нескольких
производных классов, объекты которых будут
реализованы.
В нашем случае graph – абстрактный класс.

14.

Чистые виртуальные функции
Метод draw() класса graph никогда не будет вызываться
напрямую, так как абстрактное понятие «геометрическая
фигура» не имеет конкретного визуального воплощения.
Таким визуальным воплощением обладают только
наследники класса graph (окружности, квадраты,
треугольники и т.д.).
С другой стороны, без метода draw() в базовом классе
нельзя обойтись, так как он «прототип» для всех
реальных функций прорисовки.
В этом случае метод draw() может быть объявлен чистой
виртуальной функцией.

15.

Объявление ЧВФ
Чистая виртуальная функция не содержит никаких
операторов, и вообще не имеет тела. Сразу после
объявления ЧВФ внутри класса указывается
выражение « = 0»
class имя_абстрактного_класса
{
virtual имя_чвф(аргументы) = 0;
}
Таким образом, ЧВФ представляет собой только
прототип функции, которая в последствии будет
переопределена в производных классах.

16.

Знак равенства здесь не имеет ничего общего с
операцией присваивания. Конструкция = 0 сообщает
компилятору о том, что функция является чистой
виртуальной, а сам класс – абстрактным.
class graph
{
...
public:
graph(...);
virtual void draw() = 0;
};
При попытке создать объект класса graph
компилятор выдаст сообщение об ошибке!

17.

Динамическая информация о типах
В С++ имеется возможность получать информацию о
классе объекта прямо во время выполнения
программы.
Пример с графическим приложением: Объект
какого класса (circle, square или triangle) находится
под указателем collection[i]?
Одним из механизмов получения информации о типе
объекта является использование оператора
dynamic_cast.

18.

Проверка типа – dynamic_cast
Для использования оператора dynamic_cast
компилятор должен задействовать механизм
определения типа на этапе выполнения – RTTI (RunTime Type Information).
Кроме того, в программе должна быть подключена
библиотека <typeinfo>.

19.

Определение класса объекта
Функция isCircle возвращает true, если в качестве
параметра ей передан указатель на объект класса
circle, и возвращает false, если передан указатель на
объект любого другого graph-производного класса.
bool isCircle(graph* pUnknown)
{
circle* p;
if(p = dynamic_cast<circle*>(pUnknown))
return true;
else
return false;
}
English     Русский Правила