Частина 2. Абстрактні класи та приклад використання
Давайте продовжимо почату в першій частині статті розгляд використання віртуальних функцій. На цей раз я пропоную вам конкретний приклад. Нехай дуже простий, але зате він повністю працездатний, і дозволяє продемонструвати всі тонкощі, про яких ви вже прочитали.
Але, для початку, ще трохи теорії, яка не увійшла в першу частину.
Чисті віртуальні функції
Можна подумати, що всі інші функції брудні! Ні звичайно. Чистий в даному випадку означає буквально порожня функція. Давайте подивимося, що таке чиста віртуальна функція.
Як бачите, все відмінність тільки в тому, що з'явилася конструкція = 0, яка називається чистий специфікатор. Чистий віртуальна функція абсолютно нічого не робить і недоступна для викликів. Її призначення служити основою (якщо хочете, шаблоном) для заміщають функцій в похідних класах. Клас, який містить хоча б одну чисту віртуальну функцію, називається абстрактним класом. Чому абстрактним? Тому, що створювати самостійні об'єкти такого класу не можна. Це всього лише заготовка для інших класів. Механізм абстрактних класів розроблений для представлення спільних понять, які в подальшому передбачається конкретизувати. Ці загальні поняття зазвичай неможливо використовувати безпосередньо, але на їх основі можна, як на базі, побудувати похідні приватні класи, придатні для опису конкретних об'єктів.
Приклад? Будь ласка.
Всі тварини в своїй поведінці мають такі функції, як їсти, пити, спати, видавати звук. Має сенс визначити базовий клас, в якому відразу оголосити всі ці функції і зробити їх чистими віртуальними. А потім з цього класу виводити класи, що описують конкретних тварин (або види), зі своїм специфічним поведінкою. А базовий клас при цьому дійсно виходить абстрактним. Адже він не описує ніяке більш-менш конкретне тварина (навіть вид тварин). Це може бути і риба і птиця різні речі!
Як і всякий клас, абстрактний клас може мати явно певний конструктор. З конструктора можна викликати методи класу. Але звернення з конструктора до чистих віртуальним функціям приведуть до помилок під час виконання програми.
У порівнянні зі звичайними класами, абстрактні класи користуються обмеженими правами. Як вже говорилося, неможливо створити об'єкт абстрактного класу. Абстрактний клас не можна застосовувати для завдання типу параметра функції, або в якості типу значення, що повертається. Його не можна використовувати при явному приведення типів. Зате можна визначати посилання і покажчики на абстрактні класи.
Все це, ну або майже все, ми тепер розглянемо на прикладі.
найпростіша програма
Повертаючись до войовничому наприклад з першої частини, треба сказати, що в цьому випадку це абсолютно миролюбний і навіть дитячий приклад. До речі, не дивлячись на всю його простоту, він цілком може бути основою для створення найпростішої розвиваючої гри для дітей дуже молодшого віку.
Отже. В основу покладена ідея про ознайомлення з тваринним світом. (Круто заявлено) Ні для кого не секрет (я сподіваюся), що всі тварини видають звуки. Причому різні і досить характерні для свого виду. На цьому і зіграємо.
Для спрощення прикладу обмежимося в описі кожної тварини його кличкою і типовим видаються тваринам звуком. Ну, а основний (і на жаль, єдиною) можливістю програми буде висновок на екран списку кличок тварин та подання видаваних ними звуків.
В якості базового класу ми спорудили абстрактний клас Animal. Він має єдиний член-дані Title, що описує кличку тварини. У ньому є явно певний конструктор, який присвоює тварині його ім'я. І єдина чиста віртуальна функція speak (), яка описує, які звуки видає тварина.
З цього класу виведені всі інші. Крім одного. Клас лев породжений від класу кішка (адже леви це теж кішки!). Це зроблено для демонстрації тонкощів застосування віртуальних функцій. Але про це класі трохи пізніше. А зараз як працює програма.
У всіх похідних класах описана власна заміщає віртуальна функція speak (), яка друкує на екран, які ж звуки видає конкретне тварина.
В основному тілі програми оголошений масив animals [4] покажчиків типу Animal *. І відразу ж створені динамічні об'єкти класів і заповнений масив покажчиків. А в циклі for () за вказівником просто викликається віртуальна функція speak ().
Якщо ви не зробили ніяких нових помилок при введенні програми, то висновок на екран повинен виглядати так:
А система допомоги з Borland C ++ 5 видасть наступний хелп:
Це той самий випадок, коли оголошується заміщає віртуальна функція з тим же самим типом значення, що повертається, але з іншим набором параметрів. Що у випадку зі звуками, які видає лев може бути передано функції speak (), як параметр? Припущення типу яким місцем видається звук я відкинув відразу ж і безповоротно. Припустимо, що це залежність від часу доби, тобто коли. Ну, наприклад, ближче до ночі лев захотів спати, і став позіхати. Тому в даному випадку функції speak (int When) переданий параметр When, який правда в ній ніде не використовується, але це не важливо. Функція-то все одно працювати буде.
Ну, раз програма скомпільована, треба запустити її. Що вийшло? Повинно бути наступне:
Обидва на! Здається щось не так! Лев-то у вас вже не гарчить і не позіхає, а мило нявкає. З чого б це? Але ж компілятор попереджав функція 'Lion :: speak (int)' приховує (переопределяет) віртуальну функцію 'Cat :: speak ()'. Це вже зовсім інша функція! Тому, раз в даному класі немає правильно визначеної віртуальної функції, то за вказівником викликається віртуальна функція speak () з базового класу. А в нашому випадку базовим для класу Lion є клас Cat. Ось лев у вас і занявкав!
Ось, поки що і все. Розгляд теорії віртуальних функцій в загальному випадку ми закінчили. Правда ми не розглянули ще багато різних моментів. Але це вже дуже тонкі тонкощі. Про це можна цілу книгу написати.
У наступній частині ми спробуємо розглянути кілька більш складний момент. Подивимося, що таке віртуальні деструктори класів, навіщо вони потрібні, і як з ними боротися.