
- •Содержание
- •1 Наследование
- •1.1 Типы наследования
- •1.2 Множественное наследование
- •1.3 Проблематика множественного наследования
- •1.4 Проблема ромбовидного наследования
- •1.5 Проблема ромбовидного наследования: Конструкторы и деструкторы
- •1.6 Интерфейс
- •1.7 Вывод по наследованию
- •2 Неявное приведение типов
- •4 Перегрузка
- •4.1 Правила перегрузки
- •5.1 Требования для переопределения функции
- •5.2 Привязка вызова функции к объектам класса
- •6 Виртуальные методы и таблица виртуальных методов
- •Принцип работы
- •6.1 Виртуальное наследование
- •6.2 Абстрактный класс
- •7 Виртуальные методы в конструкторе и деструкторе
- •Список использованных источников
6 Виртуальные методы и таблица виртуальных методов
Одним из важнейших приёмов реализации полиморфизма являются виртуальные методы. Они позволяют создавать общий код, который может работать как с объектами базового класса, так и с объектами любого его класса-наследника.
Виртуальный метод (виртуальная функция) — это метод класса, который может быть переопределён в классах-наследниках так, что конкретная реализация метода для вызова будет определяться во время исполнения.
Представим следующую задачу:
Необходимо создать класс «Пистолет». У данного класса реализован метод «Выстрелить», который включает в себя отображение одного выстрела.
От данного класса есть наследуемый класс «Пистолет – пулемет», который будет иметь тот же метод, что и родительский класс, но включать в себя отображение нескольких выстрелов.
Листинг № 6.1 – Программный код
#include <iostream>
class Gun /*Класс «Пистолет» с методом «Выстрелить»*/ { public: void Shoot(){ std::cout << "BANG!\n"; } }; class Gun_pul : public Gun /*Класс «Пистолет – пулемет» с методом «Выстрелить»*/ { public: void Shoot(){ std::cout << "BANG! BANG! BANG!\n"; } }; int main() { setlocale(LC_ALL, "ru"); //Gun gun; //gun.Shoot(); //Gun_pul gun_pul; //gun_pul.Shoot(); Gun* pb = new Gun_pul; pb->Shoot(); return 0; } |
В данном случае решение о том, какую функцию звать — Gun::Shoot или Gun_pul:: Shoot — принимается на основе известного компилятору типа указателя, т.е. Gun::Shoot.
Рисунок 6.1 – Результат выполнения программы
Однако, хотелось бы иметь возможность объявить наследника так, чтобы у объектов типа-наследника всегда звалась новая, определенная в типе-наследнике функция, независимо от того, какая функция определена в базовом классе.
Такое требует специальной поддержки во время выполнения, ибо компилятор, увидев Gun*, не знает, есть ли это объект, созданный как Gun, или же Gun – часть в объекте класса Gun_pul.
Для этого необходимо использовать виртуальные методы, которые объявляются с помощью оператора virtual, определяющийся в вершине дерева наследования, где он встречается впервые. Писать virtual в потомках уже не обязательно.
Листинг № 6.2 – Программный код
#include <iostream>
class Gun /*Класс Пистолет с методом "Выстрелить"*/ { public: virtual void Shoot() { std::cout << "BANG!\n"; } }; class Gun_pul : public Gun /*Класс Пистолет- пулемет с методом "Выстрелить"*/ { public: void Shoot() { std::cout << "BANG! BANG! BANG!\n"; } }; int main() { setlocale(LC_ALL, "ru"); Gun* pb = new Gun_pul; pb->Shoot();
return 0; } |
Рисунок 6.2 – Результат выполнения программы