is_реклам: Недорогие комплекты видеонаблюдения для дома . qsymia weight loss

пошук, категорії та ін. показати ▼

Віртуальні функції в C++

Віртуальні функції в C++
автор опубліковано

Зв'язування - це співставлення виклику функції з тілом. Для звичайних методів зв'язування виконується на етапі трансляції до запуску програми. Таке зв'язування називають "раннім" або статичним. При успадкування звичайного методу його поведінка не змінюється в нащадку. Але, буває необхідно, щоб поведінка деяких методів базавого класу і класів-нащадків відрізнялись. Щоб досягти різної поведінки в залежності від типу, необхідно оголосити функцію-метод віртуальною; в С++ це робиться за допомогою ключового клова virtual. Віртуальні функції разом з припципом підстановки забезпечують механізм "пізнього" (відкладеного) або динамічного зв'язування, яке працює під час виконання програми.

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

Крок 1. Успадкування батьківських методів.

Опишемо базовий клас Base, в якому є функція print(), яка виводить повідомлення на екран, що працює функція базового класу. Успадкуємо клас Base в класі Derive. Зрозуміло, що функція print() успадкується з базового класу і працюватиме так само як і в ньому.
#include <iostream.h>
#include <conio.h>

class Base				// базовий клас
        {
        public:
                void print();
        };

void Base::print()
        {
        cout<<endl<<"Base-print"<<endl;		// базовий метод
        }

class Derive:public Base			// похідний клас
        {
        };

int main(int argc, char* argv[])
{
        Base X;              // об'єкт базового класу
        Derive Y;            // об'єкт похідного класу

        X.print();              // виклик базового методу
        Y.print();              // виклик базового методу

        getch();
        return 0;
}

при клацанні на зображення, можна скачати цю програму

Крок 2. Перезавизначення успадкованих батьківських методів.

Припустимо, що ми хочемо досягти різної поведінки функції print() для об'єктів різних класів. Для цього її досить перевизначити в класі-нащадку. Якщо ім'я функції і її прототип в класі-нащадку спідпадають з іменем і протопитом батьківської функції, то говорять, що метод похідного класу "приховує" метод батьківського класу. Щоб викликати метод батьківського класу, треба вказувати його з кваліфікатором класу. Крім того, що функцію можна перевизначити (з іншим тілом), її також можна перезавантажити з іншим списком аргументів.
#include <iostream.h>
#include <conio.h>

class Base				//базовий клас
        {
        public:
                void print();
        };

void Base::print()
        {
        cout<<endl<<"Base-print"<<endl;		// базовий метод
        }

class Derive:public Base			// похідний клас
        {
        public:
                void print();
        };

void Derive::print()
        {
        cout<<endl<<"Derive-print"<<endl;		// перевизначений метод
        }

int main(int argc, char* argv[])
{
        Base X;              // об'єкт базового класу
        Derive Y;            // об'єкт похідного класу

        X.print();              // виклик базового методу
        Y.print();              // виклик похідного методу

        cout<<endl;
        Y.Base::print();   // виклик прихованого базового методу через кваліфікатор класу

        getch();
        return 0;
}

при клацанні на зображення, можна скачати цю програму

Крок 3. Функція, яка отримує параметр базового класу по посиланню

За допомогою перевизначення функції print() ми досягли того, щоб для об'єктів різних класів викликалися різні функції. Але чи завжди це буде вірно. Насправді, це лише ілюзія. Переконаймося в цьому, написавши зовнішню функцію, яка отримує параметр базового класу по посиланню і в своєму тілі просто запускає функцію print(). Викликавши цю функцію для об'єктів базового і похідного класів, за допогомою принципу підстановки ми один раз викличемо її для базового об'єкта, а інший раз - для похідного (який в узагальненні і є базовом, наприклад, спортсмен є людиною). Але чи спрацює функція print() по-різному? Насправді, ні. Тому що зв'язування відбулось на етапі трансляції, і виклик функції зв'язався з тілом базового методу. І працюватиме базовий метод для будь-яких вхідних параметрів.
#include <iostream.h>
#include <conio.h>

class Base				//базовий клас
        {
        public:
                void print();
        };

void Base::print()
        {
        cout<<endl<<"Base-print"<<endl;		// базовий метод
        }

class Derive:public Base			// похідний клас
        {
        public:
                void print();
        };

void Derive::print()
        {
        cout<<endl<<"Derive-print"<<endl;		// перевизначений метод
        }

void Fun(Base &M)		// функція, що отримує об'єкт базового класу по посиланню
        {
        M.print();		// відбувається "раннє" (статичне) зв'язування з базовим методом
        }

int main(int argc, char* argv[])
{
        Base X;              // об'єкт базового класу
        Derive Y;            // об'єкт похідного класу

        X.print();              // виклик базового методу
        Y.print();              // виклик похідного методу

        cout<<endl;
        Fun(X);	  // працює базовий метод
        Fun(Y);               // працює базовий метод !!!

        getch();
        return 0;
}

при клацанні на зображення, можна скачати цю програму

Крок 4. Віртуальні функції

Що ж потрібно зробити для того, щоб досягнути бажаного результату - щоб при виклику функції Fun(X) відбувався виклик базового методу, а при Fun(Y) - виклик похідного методу? Саме для цього і використовуються віртуальні функції. Перед описом функції print() в базовому класі ставимо ключове слово virtual. Це означає, що в функції Fun зв'язування виклику функції print() з її тілом буде відбуватися динамічно ("пізнє зв'язування") на етапі виконання програми. Тому і будуть працювати різні методи для різних вхідних даних. В цьому і полягає механізм віртуальних функцій. Віртуальну функцію можна викликати і невіртуально, якщо вказати кваліфікатор класу.
#include <iostream.h>
#include <conio.h>

class Base				// базовий клас
        {
        public:
                virtual void print();			// віртуальна функція
        };

void Base::print()
        {
        cout<<endl<<"Base-print"<<endl;		// базовий метод
        }

class Derive:public Base			// похідний клас
        {
        public:
                void print();
        };

void Derive::print()
        {
        cout<<endl<<"Derive-print"<<endl;		// перевизначений метод
        }

void Fun(Base &M)		// функція, що отримує об'єкт базового класу по посиланю
        {
        M.print();		// відбувається "пізнє" (динамічне) зв'язування, виклик віртуальної функції
        }

int main(int argc, char* argv[])
{
        Base X;              // об'єкт базового класу
        Derive Y;            // об'єкт похідного класу

        X.print();              // виклик базового методу
        Y.print();              // виклик похідного методу

        cout<<endl;
        Fun(X);	  // працює базовий метод
        Fun(Y);               // працює похідний метод !!!

        cout<<endl;
        Y.Base::print();   // невіртуальний виклик віртуальної функції, статичний виклик
	
        getch();
        return 0;
}

при клацанні на зображення, можна скачати цю програму

Зауваження.

Навіть якщо на кроці 2, описати функцію print() як віртуальну, це нічого не змінить. Тому що простим викликом функції як методу різних об'єктів неможливо продемонструвати її віртуальність.

Клас, в якому визначені віртуальні функції (хоча б одна), називається поліморфним класом.

Ключове слово virtual можна писати тільки в базовому класі - це достатньо зробити в оголошенні функції. Навіть якщо визначення писати без слова virtual, функція все одно буде вважатися віртуальною.

Правила опису і використання віртуальних функцій-методів наступні.

  1. Віртуальна функція може бути тільки методом класу.
  2. Будь-яку операцію-метод класу, яку можна перезавантажувати, можна зробити віртуальною, наприклад, операцію присвоювання або операцію переведення типу.
  3. Віртуальна функція успадковується.
  4. Віртуальна функція може бути константою.
  5. Якщо в базовому класі визначена віртуальна функція, то метод класу-нащадку з таким же іменем і прототипом (включаючи і тип значення, що повертається, і константність методу) автоматично є віртуальною (слово virtual вказувати необов'язково) і заміщає функцію-метод базового класу.
  6. Статичні методи не можуть бути віртуальними.
  7. Конструктори не можуть бути віртуальними.
  8. Деструктори можуть (частіше - повинні) бути віртуальними - це гарантує коректне звільнення пам'яті через вказівник базового класу.
class One { virtual void f(void){} }; class Two { virtual void f(void){} virtual void g(void){} }; //... cout<<sizeof(One)<<endl; // розмір = 4 cout<<sizeof(Two)<<endl; // розмір = 4

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

схоже за тегами

Коментарів 3

  1. Victoria пише: Відповіcти

    Дуже гарно описано, але головне доступно для читачів для різних рівнів знань з програмування))))

    • Ангелина пише:

      Я дуже рада, що це допомогло Вам при виконанні 6 лабораторної роботи:)

  2. Andriy пише: Відповіcти

    Дуже дякую. Допогло зрозуміти :)

Залишити коментар:

Яндекс цитирования UA TOP Bloggers