67347

ПОНЯТТЯ ПРО ВІРТУАЛЬНІ ФУНКЦІЇ ТА ПОЛІМОРФІЗМ

Лекция

Информатика, кибернетика и программирование

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

Украинкский

2014-09-07

176 KB

14 чел.

ПОНЯТТЯ ПРО ВІРТУАЛЬНІ ФУНКЦІЇ ТА ПОЛІМОРФІЗМ

Тема:  Поліморфізм. Вказівники на похідні типи

    Однією з трьох основних особливостей ООП є поліморфізм. Стосовно мови програмування C++, під терміном поліморфізм розуміють механізм реалізації функції, у якому різні результати можна отримати за допомогою одного її імені. З цієї причини поліморфізм іноді характеризується фразою "один інтерфейс, багато методів". Це означає, що до всіх функцій-членів класу можна отримати доступ одним і тим самим способом, незважаючи на можливу відмінність у конкретних діях, пов'язаних з кожною окремою операцією.

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

     Почнемо розгляд матеріалу з короткого опису вказників на похідні типи, оскільки саме вони забезпечують підтримку динамічного поліморфізму.

 Вказівники на похідні типипідтримка динамічного поліморфізму

    Основою для динамічного поліморфізму слугує показник на базовий клас. Показники на базові та похідні класи пов'язані такими відносинами, які не властиві показникам інших типів. Як правило, показник одного типу не може вказувати на об'єкт іншого типу. Проте показники на об'єкти базових класів і об'єкти похідних класів – винятки з цього правила.

   У мові програмування C++ показник на базовий клас також можна використовувати для посилання на об'єкт будь-якого класу, виведеного з базового. Наприклад, припустимо, що у нас є базовий клас baseClass і похідний клас derivClass, який виведено з класу baseClass. У мові програмування C++ будь-який показник, оголошений як показник на базовий клас baseClass, може бути також показником на похідний клас derivClass. Отже, після цих оголошень

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

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

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

обидві такі настанови є абсолютно законними:

p = &ObjB;                         // Показник p вказує на об'єкт типу baseClass

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

      /* Показник p вказує на об'єкт типу derivClass, який є об'єктом,

           що був виведений з класу baseClass */

   У наведеному прикладі показник р можна використовувати для доступу до всіх елементів об'єкта ObjD, що є виведеним з об'єкта ObjB. Проте до елементів, які становлять специфічну "надбудову" (над базою, тобто над базовим класом baseClass) об'єкта ObjD, доступ за допомогою покажчика р отримати не можна.

    Як конкретний приклад розглянемо навчальну програму, яка визначає базовий клас baseClass і похідний клас derivClass. У цьому коді програми проста ієрархія класів використовується для зберігання імен авторів і назв їх книг.

Приклад. Демонстрація механізму використання показників на базовий клас для доступу до

               об'єктів похідних класів

char bufCyr[256];

char *Cyr(const char *text)

{    CharToOem(text, bufCyr);

     return bufCyr;

}

class baseClass

{   char author[80];

  public:

      void putAuthor(char *s) { strcpy(author, s); }

      void showAuthor() { cout << Cyr("Аутор: ") << author << endl; }

};

class derivClass : public baseClass

{    char title[80];

 public:

      void putTitle(char *n) { strcpy(title, n); }

      void showTitle() { cout << Cyr("Назва: ") << title << endl; }

};

int main()

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

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

       derivClass *dp;            // Створення показника на об'єкт похідного типу

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

            // Доступ до класу baseClass через показник.

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

       bp->putAuthor(Cyr("Емiль Золя"));

             // Доступ до класу derivClass через "базовий" показник.

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

       bp->putAuthor(Cyr("Вiльям Шекспiр"));

       ObjB.showAuthor();           // Покажемо, що кожен автор належить до відповідного об'єкта.

       ObjD.showAuthor();

       cout << endl;

/* Оскільки функції putTitle() i showTitle() не є частиною базового класу, то вони недоступні через "базовий" покажчик bp, i тому до них потрібно звертатися або безпосередньо, або, як показано тут, через показник на похідний тип. */

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

       dp->putTitle(Cyr("Буря"));

       dp->showAuthor();         // Тут можна використовувати або показник bp, або показник dp.

       dp->showTitle();

       getch(); return 0;

}

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

Еміль Золя

Вільям Шекспір

Вільям Шекспір

Назва: Буря

    У наведеному прикладі показник bp визначається як показник на базовий клас baseClass. Але він може також посилатися на об'єкт похідного класу derivClass, причому його можна використовувати для доступу тільки до тих елементів похідного класу, які успадковані від базового. Проте необхідно пам'ятати, що через "базовий" показник неможливо отримати доступ до тих членів, які належать похідному класу. Ось чому до функції showTitle() звернення реалізується за допомогою показника dp, який є показником на похідний клас.

    Якщо виникає потреба за допомогою показника на базовий клас отримати доступ до елементів, що визначені певним похідним класом, то необхідно привести цей показник до типу показника на похідний тип. Наприклад, у процесі виконання цього рядка коду програми дійсно буде викликано функцію showTitle() об'єкта ObjD:

((derivClass *)bp)->showTitle();

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

    Крім цього, необхідно розуміти: хоча "базовий" показник можна використовувати для доступу до об'єктів будь-якого похідного типу, зворотне ж твердження є неправильним. Іншими словами, використовуючи показник на похідний клас, не можна отримати доступ до об'єкта базового типу. Показник можна інкрементувати та декрементувати щодо свого базового типу. Отже, якщо показник на базовий клас використовують для доступу до об'єкта похідного типу, то механізм інкрементування або декрементування не примусить його посилатися на наступний об'єкт похідного класу. Натомість він вказуватиме на наступний об'єкт базового класу. Таким чином, інкрементування або декрементування показника на базовий клас необхідно розцінювати як некоректну операцію, якщо цей показник використовують для посилання на об'єкт похідного класу.

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

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

Лекція № 18

Тема: Віртуальні функції

План

  1.  Поняття про віртуальні функції
  2.  Потреба у застосуванні віртуальних функцій

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

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

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

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

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

class baseClass 

{   public:

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

          virtual void Show() { cout << "bazovuj klass." << endl; }

};

class firstD : public baseClass

{   public:

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

         void Show() { cout << "Perwuj poxidnuj klass." << endl; }

};

class secondD : public baseClass

{   public:

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

         void Show() { cout << "Dryguj poxidnuj klass." << endl; }

};

void main()

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

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

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

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

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

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

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

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

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

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

}

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

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

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

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

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

    У класі baseClass функція Show() оголошена віртуальною. Це означає, що її можна перевизначити у похідному класі (у класі, виведеному з baseClass). І вона дійсно перевизначається в обох похідних класах firstD і secondD. У основній функції main() оголошуються чотири змінні: ObjB – об'єкт типу baseClass, bp – показник на об'єкт класу baseClass, а також два об'єкти ObjF і ObjS двох похідних класів firstD і secondD відповідно. Потім показнику bp присвоюється адреса об'єкта ObjB і викликається функція Show(). Оскільки функцію Show() оголошено віртуальною, то мова C++ у процесі виконання програми визначає, до якої саме версії функції Show() тут потрібно звернутися, причому рішення приймається шляхом аналізу типу об'єкта, який адресується показником bp. У цьому випадку показник bp вказує на об'єкт типу baseClass, тому спочатку виконується та версія функції Show(), яку оголошено у базовому класі baseClass. Потім показнику bp присвоюється адреса об'єкта ObjF.

    Як зазначалося вище, за допомогою показника на базовий клас можна звертатися до об'єкта будь-якого його похідного класу. Тому, коли функція Show() викликається удруге, мова C++ знову з'ясовує тип об'єкта, який адресує показник bp, і, виходячи з цього типу, визначає, яку версію функції Show() потрібно викликати. Оскільки показник bp тут вказує на об'єкт типу firstD, то виконується версія функції

Show(), яку визначено у похідному класі firstD. Аналогічно після присвоєння показнику bp адреси об'єкта ObjS викликається версія функції Show(), яку оголошено у

похідному класі secondD.

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

ObjF.Show();

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

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

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

"другом" іншого класу.

  1.  Потреба у застосуванні віртуальних функцій

   Як наголошувалося на початку цього розділу, віртуальні функції у поєднанні з похідними типами дають змогу мові програмування C++ підтримувати динамічний поліморфізм. Він для ООП є важливим з однієї причини: дає змогу деякому узагальненому класу визначати функції, які використовуватимуть усі похідні від нього класи, причому похідний клас може визначити власну реалізацію усіх або деяких цих функцій. Іноді ця ідея виражається так: базовий клас диктує загальний інтерфейс, який матиме будь-який об'єкт, виведений з цього класу, але при цьому дає змогу похідному класу визначити метод, який використовують для реалізації цього інтерфейсу. Ось чому для опису поліморфізму часто використовують фраза – один інтерфейс, багато методів.

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

   Тепер у Вас може виникнути запитання: чому ж такий важливий загальний інтерфейс з множиною реалізацій? Відповідь знову повертає нас до основної спонукальної причини виникнення ООП: такий ін-

терфейс дає змогу програмісту справлятися із наростаючою складністю програм. Наприклад, якщо коректно розробити програму, то можна упевнитися в тому, що до всіх об'єктів, виведених з базового класу, можна буде отримати доступ єдиним (загальним для всіх) способом, незважаючи на те, що конкретні дії одного похідного класу можуть відрізнятися від дій іншого. Це означає, що програмісту доведеться пам'ятати тільки один інтерфейс, а не велику їх кількість. Окрім цього, похідний клас має можливість використовувати будь-які або всі функції, надані базовим класом. Іншими словами, розробнику похідного класу не потрібно наново винаходити елементи, які вже є в базовому класі. Понад це, від'єднання інтерфейсу від реалізації дає змогу створювати бібліотеки класів, написанням яких можуть займатися сторонні організації. Коректно реалізовані бібліотеки повинні надавати загальний інтерфейс, який програміст може використовувати для виведення похідних класів відповідно до своїх конкретних потреб. Наприклад, як бібліотека базових класів Microsoft (Microsoft Foundation Classes – MFC), так і новіша бібліотека класів .NET Framework Windows Forms підтримують Windows-програмування. Використання цих класів дає змогу розробляти програми, які можуть успадкувати багато функцій, потрібних будь-якій Windows-програмі. Вам знадобиться тільки додати в неї засоби, унікальні для Вашої програми. Це – велика допомога при програмуванні складних ієрархічних систем.

Тема: Наслідування віртуальних функцій

    Якщо функція оголошується віртуальною, то вона залишається такою незалежно від того, через скільки рівнів похідних класів вона може пройти. Наприклад, якби похідний клас secondD був виведений з похідного класу firstD, а не з базового класу baseClass, як це показано в наведеному вище прикладі, то функція Show(), як і раніше, залишалася б віртуальною, і механізм вибору відповідної версії

теж працював би коректно. Тобто, наведений нижче приклад є коректним.

   // Цей клас виведено з класу firstD, а не з baseClass.

class secondD : public firstD

{    public:

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

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

};

Якщо похідний клас не перевизначає віртуальну функцію, то використовується функція, яку було визначено в базовому класі. Наприклад, перевіримо, як поведеться версія попереднього коду програми, якщо у похідному класі secondD не буде перевизначено функції Show().

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

class baseClass 

{   public:

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

          virtual void Show() { cout << "bazovuj klass." << endl; }

};

class firstD : public baseClass

{   public:

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

         void Show() { cout << "Perwuj poxidnuj klass." << endl; }

};

class secondD : public baseClass

{   public:

             // Функція Show() тут взагалі не визначена.

};

void main()

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

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

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

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

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

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

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

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

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

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

}

   Тепер у процесі виконання цієї програми на екран виводиться таке:

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

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

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

   

Як підтверджують отримані результати виконання цієї програми, оскільки функція Show() не перевизначена у похідному класі secondD, то під час її виклику за допомогою настанови bp->Show() (коли член р вказує на об'єкт ObjS) виконується та версія функції Show(), яку було визначено у базовому класі baseClass.

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

class baseClass 

{   public:

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

          virtual void Show() { cout << "bazovuj klass." << endl; }

};

class firstD : public baseClass

{   public:

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

         void Show() { cout << "Perwuj poxidnuj klass." << endl; }

};

// Клас secondD тепер виведений з класу firstD, а не з класу baseClass.

class secondD : public firstD

{   public:

             // Функція Show() тут взагалі не визначена.

};

void main()

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

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

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

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

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

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

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

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

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

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

}

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

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

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

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

     Як бачите, похідний клас secondD тепер використовує версію функції Show(), яку визначено у похідному класі firstD, оскільки вона знаходиться найближче в ієрархічному ланцюжку класів.

Приклад застосування віртуальних функцій

   Щоб Ви могли отримати уявлення про потужність механізму "один інтерфейс, багато методів", розглянемо таку навчальну програму. Вона створює базовий клас figure, призначений для зберігання розмірів різних двовимірних об'єктів і обчислення їх площ. Функція Set() є стандартною функцією-членом класу, оскільки вона підходить для всіх похідних класів. Проте функція Show() оголошена як ві-

ртуальна, оскільки методики обчислення площі різних об'єктів будуть різними. Програма використовує базовий клас figure для виведення двох спеціальних класів rectangle і triangle.

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

class figure

{   protected:

          double x, y;

    public:

          void Set(double _x, double _y) { x = _x; y = _y; }

          virtual void Show()

                     { cout << "У цьому класі виразу для обчислення площі не визначено." << endl;

                     }

};

class triangle : public figure

{   public:

         void Show()

                 {    cout << "Трикутник з висотою " << x; cout << " i основою " << y;

                       cout << " має площу " << x * 0.5 * y ; cout << " кв. од." << endl;

                  }

};

class rectangle : public figure

{   public:

         void Show()

                 {    cout << "Прямокутник розмірами " << x << " x " << y;

                       cout << " має площу " << x * y ; cout << " кв. од." << endl;

                  }

};

void main()

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

       triangle ObjT;            // Створення об'єктів похідних типів

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

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

       p->Set(10.3, 5.5);

       p->Show();

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

       p->Set(10.3, 5.5);

       p->Show();

}

    Ось як виглядають результати виконання цієї програми:

Трикутник з висотою 10.3 і основою 5.5 має площу 28.325 кв. од.

Прямокутник розмірами 10.3 x 5.5 має площу 56.65 кв. од.

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

    Як Ви думаєте, використовуючи оголошення базового класу figure, можна вивести похідний клас circle для обчислення площі круга за заданим значенням радіуса? Відповідь: так. Для цього достатньо створити новий похідний тип, який би обчислював площу круга. Потужність віртуальних функцій опирається на той факт, що програміст може легко вивести новий тип, який розділятиме загальний інтерфейс з іншими "спорідненими" об'єктами. Ось, наприклад, як це можна зробити в нашому випадку:

class circle : public figure

{   public:

          void Show()

                    {    cout << "Круг з радіусом " << x;

                          cout << " має площу " << 3.14 * x * x << endl;

                     }

};

Перш ніж випробувати клас circle в роботі, розглянемо уважно визначення функції Show(). Зверніть увагу на те, що в ній використовується тільки одне значення змінної x, яка повинна містити радіус круга. Проте, згідно з визначенням функції Set(), у базовому класі figure їй передається два значення, а не одне. Оскільки похідному класу circle не потрібне друге значення, то що ми можемо зробити? Є два способи вирішити цю проблему. Перший (і одночасно найгірший) полягає у тому, що ми могли б, працюючи з об'єктом класу circle, просто викликати функцію Set(), передаючи їй як другий параметр фіктивне значення. Основний недолік цього методу – відсутність чіткості у задаванні параметрів і потрібно пам'ятати про спеціальні винятки, які порушують дію механізму – один інтерфейс, багато методів.

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

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

                замовчуванням

class figure

{   protected:

          double x, y;

    public:

          void Set(double _x, double _y) { x = _x; y = _y; }

          virtual void Show()

                     { cout << "У цьому класі виразу для обчислення площі не визначено." << endl;

                     }

};

class triangle : public figure

{   public:

         void Show()

                 {    cout << "Трикутник з висотою " << x; cout << " i основою " << y;

                       cout << " має площу " << x * 0.5 * y ; cout << " кв. од." << endl;

                  }

};

class rectangle : public figure

{   public:

         void Show()

                 {    cout << "Прямокутник розмірами " << x << " x " << y;

                       cout << " має площу " << x * y ; cout << " кв. од." << endl;

                  }

};

class circle : public figure

{   public:

          void Show()

                    {    cout << "Круг з радіусом " << x;

                          cout << " має площу " << 3.14159 * x * x ; cout << " кв. од." << endl;

                     }

};

void main()

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

       triangle ObjT;            // Створення об'єктів похідних типів

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

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

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

       p->Set(10.3, 5.5);

       p->Show();

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

       p->Set(10.3, 5.5);

       p->Show();

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

       p->Set(9.67);

       p->Show();

}

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

Трикутник з висотою 10.3 і основою 5.5 має площу 28.325 кв. од.

Прямокутник розмірами 10.3 x 5.5 має площу 56.65 кв. од.

Круг з радіусом 9.67 має площу 293.767 кв. од.

 Тема: Поняття про суто віртуальні функції та абстрактні класи

    Як було зазначено вище, якщо віртуальна функція, яка не перевизначена у похідному класі, викликається об'єктом цього похідного класу, то використовується та її версія, яку було визначено в базовому класі. Але у багатьох випадках взагалі немає сенсу давати визначення віртуальної функції в базовому класі. Наприклад, в базовому класі figure (з попереднього прикладу) визначення функції Show() – це просто заглушка. Вона не обчислює і не відображає площу жодного з об'єктів. Як буде показано згодом, при створенні власних бібліотек класів, у тому, що віртуальна функція не має конкретного визначення у базовому класі, немає нічого незвичайного.

    Існує два способи оброблення таких ситуацій. Перший (він показаний у наведеному вище прикладі коду програми) полягає у виведенні функцією застережного повідомлення. Можливо, такий підхід і буде корисний у певних ситуаціях, але здебільшого він просто неприйнятний. Наприклад, можна уявити собі віртуальні функції, без визначення яких у існуванні похідного класу взагалі немає ніякого сенсу. Розглянемо клас triangle. Він абсолютно зайвий, якщо у ньому не визначити функцію Show(). У цьому випадку варто створити метод, який би гарантував, що похідний клас дійсно містить усі необхідні функції. У мові програмування C++ для вирішення цього питання і передбачено суто віртуальні функції.

    Суто віртуальна функція – це віртуальна функція, яку оголошено в базовому класі, але вона не має у ньому ніякого визначення. Тому будь-який похідний тип повинен визначити власну версію цієї функції, адже у нього просто немає ніякої можливості використовувати версію з базового класу (через її відсутність). Щоб оголосити суто віртуальну функцію, використовують такий загальний формат:

virtual тип ім'я_функції (перелік_параметрів) = 0;

   У цьому записі під елементом тип маємо на увазі тип значення, що повертається функцією, а елемент ім'я_функції – використовуване у програмі її ім'я. Позначення = 0 є ознакою того, що функція тут оголошується як суто віртуальна. Наприклад, в наступній версії визначення базового класу figure функція Show() вже представлена як суто віртуальна:

class figure

{         double x, y;

    public:

          void Set(double _x, double _y=0) { x = _x; y = _y; }

          virtual void Show() = 0;                // Суто віртуальна функція

};

   

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

Приклад. Демонстрація не коректної програми, яка у класі circle немає перевизначення функції

                Show()

class figure

{   protected:

          double x, y;

    public:

          void Set(double _x, double _y) { x = _x; y = _y; }

          virtual void Show()=0; 

};

class triangle : public figure

{   public:

         void Show()

                 {    cout << "Трикутник з висотою " << x; cout << " i основою " << y;

                       cout << " має площу " << x * 0.5 * y ; cout << " кв. од." << endl;

                  }

};

class rectangle : public figure

{   public:

         void Show()

                 {    cout << "Прямокутник розмірами " << x << " x " << y;

                       cout << " має площу " << x * y ; cout << " кв. од." << endl;

                  }

};

class circle : public figure

{         // Вiдсутнiсть визначення функцiї Show()

        // Викличе повiдомлення про помилку.};

void main()

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

       triangle ObjT;            // Створення об'єктів похідних типів

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

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

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

       p->Set(10.3, 5.5);

       p->Show();

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

       p->Set(10.3, 5.5);

       p->Show();

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

       p->Set(9.67);

       p->Show();

}

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

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


 

А также другие работы, которые могут Вас заинтересовать

51829. 14 ФЕВРАЛЯ: ВСТРЕЧАЕМ ПРАЗДНИК - МИНИ-СЦЕНАРИЙ НА ДЕНЬ ВЛЮБЛЕННЫХ 43.5 KB
  Пусть в каждой мелочи чувствуется дыхание любви.Не сращивает кость не очищает кровьНо без любви порою умираютДорогие друзья Несмотря на то что все бури страстей ненависть дружба секс и многое другое вмещаются в одно только слово из шести букв мы уже видим что любовь бывает разная. Сейчас будут исполнены песни о разной любви. Вы слушайте внимательно а потом ответьте на вопрос о какой любви шла речь.