Віртуальні функції. Що це таке? Частина 2 (исходники), Різне, Програмування, статті

Частина 1


Частина 2. Абстрактні класи і приклад використання


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



Чисті віртуальні функції


Можна подумати, що всі інші функції брудні! Ні, звичайно. Чистий в даному випадку означає буквально порожня функція. Давайте подивимося, що таке чиста віртуальна функція.

Код:


class A
{
  public:
virtual void v_function (void) = 0 ;/ / чистий віртуальна функція
};

Як бачите, все відмінність тільки в тому, що з’явилася конструкція = 0, яка називається чистий специфікатор. Чистий віртуальна функція абсолютно нічого не робить і недоступна для викликів. Її призначення служити основою (якщо хочете, шаблоном) для заміщуючих функцій в похідних класах. Клас, який містить хоча б одну чисту віртуальну функцію, називається абстрактним класом. Чому абстрактним? Тому, що створювати самостійні об’єкти такого класу не можна. Це всього лише заготовка для інших класів. Механізм абстрактних класів розроблений для представлення загальних понять, які в подальшому передбачається конкретизувати. Ці загальні поняття зазвичай неможливо використовувати безпосередньо, але на їх основі можна, як на базі, побудувати похідні приватні класи, придатні для опису конкретних об’єктів.
Приклад? Будь ласка.

Всі тварини в своїй поведінці мають такі функції, як їсти, пити, спати, видавати звук. Має сенс визначити базовий клас, в якому відразу оголосити всі ці функції і зробити їх чистими віртуальними. А потім з цього класу виводити класи, описують конкретних тварин (або види), зі своїм специфічним поведінкою. А базовий клас при цьому дійсно виходить абстрактним. Адже він не описує ніяке більш-менш конкретне тварину (навіть вид тварин). Це може бути і риба і птиця різні речі!
Як і всякий клас, абстрактний клас може мати явно певний конструктор. З конструктора можна викликати методи класу. Але звернення з конструктора до чистих віртуальним функцій приведуть до помилок під час виконання програми.
У порівнянні зі звичайними класами, абстрактні класи користуються обмеженими правами. Як уже говорилося, неможливо створити об’єкт абстрактного класу. Абстрактний клас не можна застосовувати для завдання типу параметра функції, або в якості типу значення, що повертається. Його не можна використовувати при явному приведення типів. Зате можна визначати посилання і покажчики на абстрактні класи.
Все це, ну або майже все, ми тепер розглянемо на прикладі.


Найпростіша програма


Повертаючись до войовничому наприклад з першої частини, треба сказати, що в цьому випадку це абсолютно миролюбний і навіть дитячий приклад. До речі, не дивлячись на всю його простоту, він цілком може бути основою для створення найпростішої розвиваючої гри для дітей дуже молодшого віку.
Отже. В основу покладено ідею про ознайомлення з тваринним світом. (Круто заявлено) Ні для кого не секрет (я сподіваюся), що всі тварини видають звуки. Причому різні і досить характерні для свого виду. На цьому і зіграємо.
Для спрощення прикладу обмежимося в описі кожної тварини його кличкою і типовим видаваним тваринам звуком. Ну, а основний (і на жаль, єдиною) можливістю програми буде висновок на екран списку кличок тварин та подання видаваних ними звуків.

Код:


#include <iostream.h>

/ / Абстрактний базовий клас
class Animal
{
  public:
char * Title; / / кличка тварини
Animal (char * t) {Title = t;} / / простий конструктор
virtual void speak (void) = 0; / / чистий віртуальна функція
};

/ / Клас жаба
class Frog: public Animal
{
  public:
    Frog(char *Title): Animal(Title) { };
virtual void speak (void) {cout << Title << "говорить" << "'ква-ква'" << endl;};
};

/ / Клас собака
class Dog: public Animal
{
  public:
    Dog(char *Title): Animal(Title) { };
virtual void speak (void) {cout << Title << "говорить" << "'гав-гав'" << endl;};
};

/ / Клас кішка
class Cat: public Animal
{
  public:
    Cat(char *Title): Animal(Title) { };
virtual void speak (void) {cout << Title << "говорить" << "'мяу-мяу'" << endl;};
};

/ / Клас лев
class Lion: public Cat
{
  public:
    Lion(char *Title): Cat(Title) { };
virtual void speak (void) {cout << Title << "говорить" << "'ррр-ррр'" << endl;};
/ / Virtual int speak (void) {cout << Title << "говорить" << "'ррр-ррр'" << endl; return 0;};
/ / Virtual void speak (int When) {cout << Title << "говорить" << "'ООА-ооу'" << endl;};
};

int main ()
{
/ / Оголосимо масив покажчиків на базовий клас Animal
/ / І відразу його заповнимо покажчиками, створюючи об’єкти
Animal * animals [4] = {new Dog (“Бобик”),
new Cat (“Мурка”),
new Frog (“Керміт”),
new Lion (“Кінг”)}; / / cписок тварин

 for(int k=0; k<4; k++)  animals[k]->speak();
 return 0;
}


В якості базового класу ми спорудили абстрактний клас Animal. Він має єдиний член-дані Title, що описує кличку тварини. В ньому є явно певний конструктор, який присвоює тварині його ім’я. І єдина чиста віртуальна функція speak (), яка описує, які звуки видає тварина.
З цього класу виведені всі інші. Крім одного. Клас лев породжений від класу кішка (адже леви це теж кішки!). Це зроблено для демонстрації тонкощів застосування віртуальних функцій. Але про це класі трохи пізніше. А зараз як працює програма.
У всіх похідних класах описана власна замещающая віртуальна функція speak (), яка друкує на екран, які ж звуки видає конкретне тварину.
В основному тілі програми оголошений масив animals [4] покажчиків типу Animal *. І відразу ж створені динамічні об’єкти класів і заповнений масив покажчиків. А в циклі for () за вказівником просто викликається віртуальна функція speak ().
Якщо ви не зробили жодних нових помилок при введенні програми, то вивід на екран повинен виглядати так:

Код:


Бобик говорить гав-гав
Мурка каже мяу-мяу
Керміт говорить ква-ква
Кінг каже ррр-ррр

Все працює. Кожен об’єкт сам виводить свій запис. Віртуальні функції діють!
А тепер повернемося до опису класу Lion (лев).
У ньому замість однієї віртуальної функції speak () міститься відразу три. Правда дві з них закоментовані. Якщо ви закомментіруете першу функцію, а раскомментіруете другу, то зможете перевірити варіант, коли проводиться спроба спорудити віртуальну замещающую функцію з іншим типом значення, що повертається. В даному випадку друга (неправильна) функція повертає тип int замість типу void, який був у функції speak () в базовому класі. Спробуйте скомпілювати програму компілятор відразу ж пред’явить вам претензії з приводу:

Код:


Error:  animals.cpp(42,25):Virtual function 'Lion::speak()' conflicts with base class 'Cat'

А система допомоги з Borland C + + 5 видасть наступний хелп:

Код:


A virtual function has the same argument types as one in a base class, but a different return type. This is illegal.
Тобто віртуальна функція має той же аргумент, що і в базовому класі, але повертає інший тип. Це неприпустимо.

Далі ще цікавіше. Спробуйте розкоментувати третю функцію, а перші дві закоментуйте. Компілятор цього разу просто видасть попередження (але не помилку, і програма буде працювати!):

Код:


Warn :  animals.cpp(44,3):'Lion::speak(int)' hides virtual function 'Cat::speak()'

Це той самий випадок, коли оголошується замещающая віртуальна функція з тим же самим типом значення, що повертається, але з іншим набором параметрів. Що у випадку зі звуками, які видає лев може бути передано функції speak (), як параметр? Припущення типу яким місцем видається звук я відмів відразу ж і безповоротно. Припустимо, що це залежність від часу доби, тобто коли. Ну, наприклад, ближче до ночі лев захотів спати, і став позіхати. Тому в даному випадку функції speak (int When) переданий параметр When, який правда в ній ніде не використовується, але це не важливо. Функція-то все одно працювати буде.
Ну, раз програма скомпільована, треба запустити її. Що вийшло? Повинно бути наступне:

Код:


Бобик говорить гав-гав
Мурка каже мяу-мяу
Керміт говорить ква-ква
Кінг каже мяу-мяу

Оба-на! Здається щось не так! Лев-то у вас вже не гарчить і не позіхає, а мило нявкає. З чого б це? Але ж компілятор попереджав функція ‘Lion :: speak (int)’ приховує (перевизначає) віртуальну функцію ‘Cat :: speak ()’. Це вже зовсім інша функція! Тому, раз в даному класі немає правильно певної віртуальної функції, то за вказівником викликається віртуальна функція speak () з базового класу. А в нашому випадку базовим для класу Lion є клас Cat. Ось лев у вас і занявкав!

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

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*