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

Void Show()

{

cout« Суг("Трикутник з висотою ")« x; cout« Cyr(" і основою") « у; cout« Cyr(" мас площу ") « x * 0.5 * у ; cout« Суг(" кв. од. ")« endl;

}

};

class rectangle: public figure { public:

Void Show()

{

cout« Суг("Прямокутник розмірами") « x «" x" « y; cout« Cyr(" мас площу ") «х* у; cout« Cyr(" кв. од. ")« endl;

}

};

class circle: public figure {

// Відсутність визначення функції ShowO // Викличе повідомлення про помилку.

};

Int mainO

// Створення покажчика на об'єкт базового типу // Створення об'єкта похідного типу // Створення об'єкта похідного типу // Помилка: створення цього об'єкта є неможливим!

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

{

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

figure *р; triangle ObjT; rectangle ObjR; circle ObjC; p = &ObjT; p->Set(10.3,5.5); p->ShowO; p = &ObjR; p->Set(10.3,5.5); p->ShowO; getchO; return 0;

_}

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

Абстрактний клас характеризує одна важлива особливість: у такого класу не може бути об'єктів. Абстрактний клас можна використовувати тільки як базовий,

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

  1. Порівняння механізму раннього зв'язування з пізнім

Під час обговорення об'єктно-орієнтованих мов програмування зазвичай ви­користовують два терміни: раннє зв’язування (early binding) і пізнє зв'язування (la­te binding). У мові програмування C++ ці терміни пов'язують з подіями, які відбу­ваються при компілюванні та у період виконання програми відповідно.

При ранньому зв'язуванні виклик функцгі готується при компілюванні програми, а при пізньому ~ у процесі виконання програми.

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

Пізнє зв'язування означає, що точне рішення про виклик функції буде ухвале­но у процесі виконання програми. Пізнє зв'язування у мові програмування C++

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

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

  1. Поняття про поліморфізм і пуризм

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

Деякі пуристи (у цьому випадку - борці за чистоту термінології) об'єктно- орієнтованого програмування наполягають на тому, щоб цей термін використову­вався тільки для подій, які відбуваються у процесі виконання програм. Вони стве­рджують, що поліморфізм підтримується тільки віртуальними функціями. Частко­во ця точка зору Грунтується на тому факті, що найпершими поліморфічними мо­вами програмування були інтерпретатори (для них характерним є те, що всі події належать тривалості виконання програми). Поява трансльованих поліморфічних мов програмування розширила концепцію реалізації поліморфізму. Однак все ще не утихають заяви про те, що термін поліморфізм повинен застосовуватися вик­лючно до подій періоду виконання програми. Більшість С++-програмістів не по­годжуються з цією точкою зору і вважають, що цей термін можна застосовувати до обох видів засобів. Тому Ви не повинні дивуватися, якщо хтось раптом стане сперечатися з Вами на предмет використання цього терміна!

1 Насправді в більшості крупних програм використовуються обидва види зв'язування. Ю.І. Грицнж, Т.Є. Рак

Розділ 7. РОБОТА З ШАБЛОННИМИ ФУНКЦІЯМИ ТА КЛАСАМИ

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

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

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

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

Узагальнена функція — це функція, яка перевизначає сама себе.

Узагальнена функція створюється за допомогою ключового слова template. Звичайне значення слова "template" точно відображає мету його застосування у мо­ві програмування C++. Це ключове слово використовують для створення шаблону (або оболонки), який описує дії, виконувані функцією. Компіляторові ж зали­шається "доповнити відсутні деталі" відповідно до заданого значення параметра. Загальний формат визначення шаблонної функції має такий вигляд:

template <class tType> тип ім'я_функи/і(перелік_параметрів)

{

// тіло функції

}

У цьому записі елемент tType є "заповнювачем" для типу даних, які обробля­ються функцією. Це ім'я використовується в тілі самої функції. Але воно означає всього тільки заповнювач, замість якого компілятор автоматично підставить ре­альний тип даних при створенні конкретної версії функції. І хоча для задавання узагальненого типу в template-оголошенні за традицією застосовується ключове слово class, однак можна також використовувати ключове слово typename.

      1. Механізм реалізації шаблонної функції з одним узагальненим типом

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

Код програми 7.1. Демонстрація механізму застосування шаблонної функції з одним узагальненим типом

#include <vcl>

#include <iostream> // Для потокового введення-виведення

#include <conio> // Для консольного режиму роботи

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

// Визначення шаблонної функції.

template <class аТуре> void swapAB(aType &а, аТуре &b)

{

аТуре tmp; // Створення тимчасової змінної tmp = а; а = Ь; b = tmp;

}

int mainO

{

int і = 10, j = 20; double x = 10.1, у = 23.3; char a = 'x', b = 'z';

cout«"Початкові значення і, j:" « і « "" «j« endl; cout«"Початкові значення x, у:x «""« у « endl; cout«"Початкові значення a, b:" « a «"" « b « endl;

swapAB(i, j); // Перестановка цілих чисел swapAB(x, у); // Перестановка чисел з плинною крапкою swapAB(a, b); // Перестановка символів

cout«"Після перестановки і, j:" « і «"" «j « endl; cout«"Після перестановки х, у:" « х «"" « у « endl; cout«"Після перестановки а, Ь:" « а «"" « b « endl; getchO; return 0;

}

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

Початкові значення і, j: 10 20

Початкові значення х, у: 10.1 23.3

Початкові значення a, b: х z

Після перестановки і, j: 20 10

Після перестановки х, у: 23.310.1

Після перестановки a, b: z х

Отже, розглянемо уважно код програми. Рядок template <class аТуре> void swapAB(aType &а, аТуре &b)

повідомляє компілятор, по-перше, що створюється шаблон, і, по-друге, що тут по­чинається узагальнене визначення шаблонної функції. Позначення аТуре є узагаль­неним типом, який використовується як "заповнювач". За template-заголовком зна­ходиться оголошення функції swapABO, у якому символ аТуре означає тип даних для значень, які мінятимуться місцями. У функції main() продемонстровано виклик функції swapABO з використанням трьох різних типів даних: int, double і char. Ос­кільки функція swapAB() є узагальненою, то компілятор автоматично створює три версії функції swapABO: одну для обміну цілих чисел, другу для обміну чисел з плинною крапкою і третю для обміну символів.

Тут необхідно уточнити деякі важливі терміни, пов'язані з шаблонами. По- перше, узагальнена функція (тобто функція, оголошення якої передує template-Hac- танові) також називається шаблонною функцією. Обидва терміни використову­ються у цьому навчальному посібнику як взаємозамінні. Коли компілятор ство­рює конкретну версію цієї функції, то вважають, що створюється її спеціалізація (або конкретизація). Спеціалізація також називається породженою функцією (ge­nerated function). Дію породження функції визначають як її реалізацію (instantia­ting). Іншими словами, породжувана функція є конкретним примірником шаблон­ної функції.

Оскільки мова програмування C++ не розпізнає символ кінця рядка як ознаку кінця настанови, то template-частина визначення узагальненої функції може не зна­ходитися в одному рядку з іменем цієї функції. У наведеному нижче прикладі по­казано ще один (достатньо поширений) спосіб форматування функції swapABO: template <class аТуре> void swapAB(aType &a, аТуре &b)

{

аТуре tmp; // Створення тимчасової змінної

tmp = а; а = b; b = tmp;

}

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

// Цей програмний код не відкомпілюсться template <class аТуре> int і;//Тут помилка!