Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
+ООП_Навч_посібник.doc
Скачиваний:
7
Добавлен:
01.07.2025
Размер:
6.58 Mб
Скачать

16.2. Віртуальні функції

Динамічний поліморфізм можливий завдяки поєднанню двох засобів: механізму успадкування і застосування віртуальних функцій. Про механізм успадкування було сказано у попередньому розділі. Тут ми познайомимося з віртуальними функціями.

16.2.1. Поняття про віртуальні функції

Віртуальна функція – це функція, яка оголошується в базовому класі з використанням ключового слова virtual і перевизначається в одному або декількох похідних класах. Таким чином, кожен похідний клас може мати власну версію віртуальної функції. Цікаво розглянути ситуацію, коли віртуальна функція викликається через покажчик (або посилання) на базовий клас. У цьому випадку мова програмування C++ визначає, яку саме версію віртуальної функції необхідно викликати, за типом об'єкта, яка адресується цим покажчиком. Причому необхідно мати на увазі, що це рішення приймається у процесі виконання програми. Отже, при вказанні на різні об'єкти викликатимуться і різні версії віртуальної функції. Іншими словами, саме за типом об'єкта (а не за типом самого покажчика), яка адресується, визначається, яку версію віртуальної функції буде виконано. Таким чином, якщо базовий клас містить віртуальну функцію і якщо з цього базового класу виведено два (або більше) інші класи, то при адресації різних типів об'єктів через покажчик на базовий клас виконуватимуться і різні версії віртуальної функції. Аналогічний механізм працює і під час використання посилання на базовий клас.

Щоб оголосити функцію віртуальною, достатньо, щоб її оголошенню передувало ключове слово virtual.

Функція оголошується віртуальною в базовому класі за допомогою ключового слова virtual. Під час перевизначення віртуальної функції у похідному класі ключове слово virtual повторювати не потрібно (хоча це не буде помилкою).

Клас, який містить віртуальну функцію, називається поліморфним класом.

Цей термін також застосовується до класу, який успадковує базовий клас, що містить віртуальну функцію. Розглянемо наведену нижче коротку програму, у якій продемонстровано механізм використання віртуальних функцій.

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

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

class baseClass { // Оголошення базового класу

public:

// Оголошення віртуальної функції

virtual void who() { cout << "Базовий клас.\n";}

};

class firstD : public baseClass {

public:

// Перевизначення функції who() для класу firstD.

void who() { cout << "Перший похідний клас.\n";}

};

class secondD : public baseClass {

public:

// Перевизначення функції who() для класу secondD.

void who() { cout << "Другий похідний клас.\n";}

};

int main()

{

baseClass Base_ob; // Створення об'єкта базового типу

baseClass *p; // Створення покажчика на об'єкт базового типу

firstD First_ob; // Створення об'єкта похідного типу

secondD Second_ob; // Створення об'єкта похідного типу

p = &Base_ob; // Присвоєння адреси об'єкта базового класу

p->who(); // Доступ до функції who() класу baseClass

p = &First_ob; // Присвоєння адреси об'єкта похідного класу

p->who(); // Доступ до функції who() класу firstD

p = &Second_ob; // Присвоєння адреси об'єкта похідного класу

p->who(); // Доступ до функції who() класу secondD

getch(); return 0;

}

У процесі виконання ця програма відображає на екрані такі результати:

Базовий клас.

Перший похідний клас.

Другий похідний клас.

Тепер розглянемо код цієї програми детально, щоб зрозуміти, як вона працює.

У класі baseClass функція who() оголошена віртуальною. Це означає, що її можна перевизначити у похідному класі (у класі, виведеному з baseClass). І вона дійсно перевизначається в обох похідних класах firstD і secondD. У функції main() оголошуються чотири змінні: Base_ob (об'єкт типу baseClass), p (покажчик на об'єкт класу baseClass), а також два об'єкти First_ob і Second_ob двох похідних класів firstD і secondD відповідно. Потім покажчику р присвоюється адреса об'єкта Base_ob і викликається функція who(). Оскільки функцію who() оголошено віртуальною, то мова C++ у процесі виконання програми визначає, до якої саме версії функції who() тут потрібно звернутися, причому рішення приймається шляхом аналізу типу об'єкта, яка адресується покажчиком р. У цьому випадку р вказує на об'єкт типу baseClass, тому спочатку виконується та версія функції who(), яку оголошено у класі baseClass. Потім покажчику р присвоюється адреса об'єкта First_ob. Пригадайте, що за допомогою покажчика на базовий клас можна звертатися до об'єкта будь-якого його похідного класу. Тому, коли функція who() викликається удруге, мова C++ знову з'ясовує тип об'єкта, який адресує покажчик р, і, виходячи з цього типу, визначає, яку версію функції who() потрібно викликати. Оскільки р тут вказує на об'єкт типу firstD, то виконується версія функції who(), визначену у класі firstD. Аналогічно після присвоєння покажчику р адреси об'єкта Second_ob викликається версія функції who(), оголошена у класі secondD.

Необхідно пам'ятати! Те, яка версія віртуальної функції дійсно буде викликана, визначається у процесі виконання програми. Рішення ґрунтується виключно на аналізі типу об'єкта, яка адресується покажчиком на базовий клас.

Віртуальну функцію можна викликати звичайним способом (не через покажчик), використовуючи оператор "крапка" і задаючи ім'я об'єкта, що викликається. Це означає, що у попередньому прикладі було б синтаксично коректно звернутися до функції who() за допомогою такої настанови:

First_ob.who();

Проте під час виклику віртуальної функції у такий спосіб ігноруються її поліморфні атрибути. І тільки під час звернення до віртуальної функції через покажчик на базовий клас досягається динамічний поліморфізм.

Якщо віртуальна функція перевизначається у похідному класі, то її називають перевизначеною.

Спочатку може видатися, що перевизначення віртуальної функції у похідному класі є спеціальною формою перевантаження функцій. Але це не так. Насправді ми маємо справу з двома принципово різними процесами. Передусім, версії перевантаженої функції повинні відрізнятися одна від іншої типом і/або кількістю параметрів, тоді як тип і кількість параметрів у версіях віртуальної функції повинні точно збігатися. І справді, прототипи віртуальної функції та її перевизначення мають бути абсолютно однаковими. Якщо прототипи будуть різними, то така функція просто вважатиметься перевантаженою, а її "віртуальна суть" втратиться. Окрім того, віртуальна функція повинна бути членом класу, для якого вона визначається, а не його "другом". Але водночас віртуальна функція може бути "другом" іншого класу.

Варто знати! Функціям деструкторів дозволено бути віртуальними, а функціям конструкторів – ні.