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

Int mainO

{

derivedC ObjC;

ObjC.c = 10; //Тепер неоднозначності немає.

ObjC.d = 20;

ObjC.f = ЗО;

//Тепер неоднозначності немає.

ObjC.sum = ObjC.c + ObjC.d + ObjC.f;

//Тепер неоднозначності немає.

cout« ObjC.c «"";

cout« ObjC.d «"" « ObjC.f«"";

cout« ObjC.sum;

getchO; return 0;

}

Як бачите, ключове слово virtual передує решті частини специфікації успадко­ваного класу. Тепер обидва класи derivedA і derivedB успадковують клас baseClass як віртуальний, і тому при будь-якому багаторазовому їх успадкуванні у похідний клас буде включено тільки одну його копію. Отже, у класі derivedC є тільки одна копія класу baseClass, а настанова ObjC.c = 10 тепер абсолютно допускається і не мі­стить ніякої неоднозначності.

Нео! хгдноапам'ятати! Навіть якщо обидва класи derivedA і derivedB задають клас baseClass як virtual-клас, він, як і раніше, присутній в об'єкті будь-якого типу.

Наприклад, така послідовність настанов цілком допускається:

// Оголошення похідного класу типу derivedA.

derivedA myClass;

myClass.c = 88;

Різниця між звичайним базовим і віртуальним класами стає очевидною тіль­ки тоді, коли цей базовий клас успадковується більше одного разу. Якщо базовий клас оголошується віртуальним, то тільки один його примірник буде включено в об'єкт класу, що успадковується. Інакше у цьому об'єкті будуть міститися декіль­ка його копій.

Розділ в. ПОНЯТТЯ ПРО ВІРТУАЛЬНІ ФУНКЦІЇ ТА ПОЛІМОРФІЗМ

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

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

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

  1. Покажчики на похідні типи - підтримка динамічного поліморфізму

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

У мові програмування С++ покажчик на базовий клас також можна викорис­товувати для посилання на об'єкт будь-якого класу, виведеного з базового. Нап­риклад, припустимо, що у нас є базовий клас ЬазеСІазБ і похідний клас сіегіуСІазБ, який виведено з класу ЬазеСІазБ. У мові програмування С++ будь-який покажчик, оголошений як покажчик на базовий клас ЬазеСІазБ, може бути також покажчиком на похідний клас сіегІуСІазБ. Отже, після цих оголошень ЬазеСІазБ *р; // Створення покажчика на об'єкт базового типу ЬазеСІазБ ОЬІВ; // Створення об'єкта базового типу сіегк/СІазБ ОЬІО; // Створення об'єкта похідного типу

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

р = &ОЬІВ; // Покажчик р вказує на об'єкт типу ЬазеСІазБ р = &ОЬІО; // Присвоєння покажчику адреси об'єкта похідного класу сіегіуСІазБ

І* Покажчик р вказує на об'єкт типу derivClass, який є об'єктом, що був виведений з класу baseClass */

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

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

Код програми 6.1. Демонстрація механізму використання покажчиків на базовий клас для доступу до об'єктів похідних класів

#include <vcl>

#include <conio>

#include <iostream> // Для потокового введення-виведення #include <cstring> // Для роботи з рядковими типами даних

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

#include <windows> 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 showAuthorO {cout« Суг("Аутор:") « author« endl;}

};

class derivClass: public baseClass { // Оголошення класового типу char title[80]; public:

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

void showTitleO {cout « Суг("Назва:") «title « endl;}

};

int mainO

{

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

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

bp->putAuthor(Cyr("EMmb Золя"));

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

bp->putAuthor(Cyr("Bm^M Шекспір"));

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

ObjD.showAuthorO;

cout« endl;

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

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

dp->putTitle(Cyr("Бypя"));

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

getchO; return 0;

}

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

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

Вільям Шекспір Назва: Буря

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

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

((derivClass *)bp)->showTitleO;

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

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

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

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

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

  1. Механізми реалізації віртуальних функцій

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

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

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

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

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

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

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

Код програми 6.2. Демонстрація механізму застосування віртуальних функцій #include <vcl>

#include <conio>

#include <iostream> // Для потокового введення-виведення using namespace std; // Використання стандартного простору імен

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

public:

// Оголошення віртуальної функції virtual void Show() {cout« Суг("Базовий клас.") « endl;}

};

class firstD : public baseClass { public:

// Перевизначення функції Show() для класу firstD. void Show() {cout« Суг("Перший похідний клас. ")« endl;}

};

class secondD : public baseClass { public:

// Перевизначення функції Show() для класу secondD. void Show() {cout« Суг("Другий похідний клас.") « endl;}

};