Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Namespace CounterNameSpace 6 страница




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

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

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


Розділ 17. Шаблони в класах

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

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

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

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

Узагальнена функція – це функція, яка перевантажує сама себе.

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

template < class Ttype> тип ім'я_функції (список_параметрів)

{

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

}

Визначення узагальненої функції починається з ключового слова template.

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

17.1.1. Шаблонна функція з одним узагальненим типом

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

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

#include <vcl>

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

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

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

 

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

template < class aType> void swapAB(aType &a_ob, aType &b_ob)

{

aType tmp; // Створення тимчасового об'єкта

tmp = a_ob;

a_ob = b_ob;

b_ob = tmp;

}

 

int main ()

{

int i = 10, j = 20;

double x = 10.1, y = 23.3;

char a = 'x', b = 'z';

cout << "Початкові значення i, j: " << i << " " << j << "\n";

cout << "Початкові значення x, y: " << x << " " << y << "\n";

cout << "Початкові значення а, b: " << a << " " << b << "\n";

 

swapAB(i, j); // перестановка цілих чисел

swapAB(x, y); // перестановка чисел з плинною крапкою

swapAB(a, b); // перестановка символів

cout << "Після перестановки i, j: " << i << " " << j << "\n";

cout << "Після перестановки x, y: " << x << " " << y << "\n";

cout << "Після перестановки а, b: " << a << " " << b << "\n";

 

getch (); return 0;

}

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

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

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

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

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

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

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

Отже, розглянемо уважно код програми. Рядок

template < class aType> void swapAB(aType &a_ob, aType &b_ob)

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

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

Оскільки мова програмування C++ не розпізнає символ кінця рядка як ознаку кінця настанови, то template -частина визначення узагальненої функції може не знаходитися в одному рядку з іменем цієї функції. У наведеному нижче прикладі показано ще один (достатньо поширений) спосіб форматування функції swapAB():

template < class aType> void swapAB(aType &a_ob, aType &b_ob)

{

aType tmp; // Створення тимчасового об'єкта

 

tmp = a_ob;

a_ob = b_ob;

b_ob = tmp;

}

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

// Цей програмний код не скомпілюється

template < class aType>

int i; // Тут помилка!

void swapAB(aType &a_ob, aType &b_ob)

{

aType tmp; // Створення тимчасового об'єкта

 

tmp = a_ob;

a_ob = b_ob;

b_ob = tmp;

}

Як зазначено в коментарі, template -специфікація повинна знаходитися безпосередньо перед визначенням функції. Між ними не може знаходитися ні настанова оголошення змінної, ні будь-яка інша настанова.

17.1.2. Шаблонна функція з двома узагальненими типами

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

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

#include <vcl>

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

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

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

 

template < class aType, class bType> void fun_c(aType a_ob, bType b_ob)

{

cout << a_ob << " " << b_ob << "\n";

}

 

int main ()

{

fun_c(10, "Привіт");

 

fun_c(0.23, 10L);

 

getch (); return 0;

}

У наведеному прикладі у процесі виконання функції main (), коли компілятор генерує конкретні примірники функції fun_c(), заповнювачі типів aType і bType замінюються спочатку парою типів даних int і char *, а потім парою double і long відповідно.

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

17.1.3. Безпосередньо задане перевантаження узагальненої функції

"Вручну" перевантажена версія узагальненої функції називається безпосередньою спеціалізацією.

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

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

#include <vcl>

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

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

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

 

template < class aType> void swapAB(aType &a_ob, aType &b_ob)

{

aType tmp; // Створення тимчасового об'єкта

 

tmp = a_ob;

a_ob = b_ob;

b_ob = tmp;

cout << "Виконується шаблонна функція swapAB.\n";

}

 

// Ця функція перевизначає узагальнену версію функції

// swapAB() для int -параметрів.

void swapAB(int &a, int &b)

{

int tmp;

 

tmp = a;

a = b;

b = tmp;

cout << "Це int -спеціалізація функції swapAB.\n";

}

 

int main ()

{

int i = 10, j = 20;

double x = 10.1, y = 23.3;

char a = 'x', b = 'z';

cout << "Початкові значення i, j: " << i << " " << j << "\n";

cout << "Початкові значення x, y: " << x << " " << y << "\n";

cout << "Початкові значення а, b: " << a << " " << b << "\n";

swapAB(i, j); // Викликається безпосередньо перевантажена

// функція swapAB().

swapAB(x, y); // Викликається узагальнена функція swapAB().

swapAB(a, b); // Викликається узагальнена функція swapAB().

 

cout << "Після перестановки i, j: " << i << " " << j << "\n";

cout << "Після перестановки x, y: " << x << " " << y << "\n";

cout << "Після перестановки а, b: " << a << " " << b << "\n";

 

getch (); return 0;

}

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

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

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

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

Це int-спеціалізація функції swapAB.

Виконується шаблонна функція swapAB.

Виконується шаблонна функція swapAB.

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

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

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

Як зазначено в коментарях до цієї програми, під час виклику функції swapAB(i, j) виконується безпосередньо перевантажена версія функції swapAB(), яка є визначена у програмі. Компілятор у цьому випадку не генерує цю версію узагальненої функції swapAB(), оскільки узагальнена функція перевизначається безпосередньо заданим варіантом перевантаженої функції.

Для позначення безпосередньої спеціалізації функції можна використовувати новий альтернативний синтаксис, що містить ключове слово template. Наприклад, якщо задати спеціалізацію з використанням цього альтернативного синтаксису, то перевантажена версія функції swapAB() з попередньої програми виглядатиме так:

// Використання нового синтаксису задавання спеціалізації

template <> void swapAB< int >(int &a, int &b)

{

int tmp;

 

tmp = а;

a = b;

b = tmp;

cout << "Це int -спеціалізація функції swapAB.\n";

}

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

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

17.1.4. Перевантаження шаблону функції

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

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

#include <vcl>

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

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

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

 

// Перша версія шаблону fun_f().

template < class aType> void fun_f(aType a_ob)

{

cout << "Виконується функція fun_f(aType a_ob)\n";

}

 

// Друга версія шаблону fun_f().

template < class aType, class bType> void fun_f(aType a_ob, bType b_ob)

{

cout << "Виконується функція fun_f(aType a_ob, bType b_ob)\n";

}

 

int main ()

{

fun_f(10); // Викликається функція fun_f(a).

fun_f(10, 20); // Викликається функція fun_f(a, b).

 

getch (); return 0;

}

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

17.1.5. Використання стандартних параметрів у шаблонних функціях

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

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

#include <vcl>

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

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

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

 

// Відображення даних задану кількість разів.

template < class aType> void repeat(aType data, int times)

{

int tim = times;

do {

cout << tim – times + 1 << " ==> " << data << "\n";

times--;

} while (times);

cout << "\n";

}

 

int main ()

{

repeat("Це тест.", 3);

repeat(100, 5);

repeat(99.0/2, 4);

 

getch (); return 0;

}

Ось які результати генерує ця програма.

1 ==> Це тест.

2 ==> Це тест.

3 ==> Це тест.

 

1 ==> 100

2 ==> 100

3 ==> 100

4 ==> 100

5 ==> 100

 

1 ==> 49.5

2 ==> 49.5

3 ==> 49.5

4 ==> 49.5

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

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

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

void outdata(int i)

{

cout << i << "\n";

}

 

void outdata(double d)

{

cout << d * 3.1416 << "\n";

}

17.1.7. Приклад створення узагальненої функції abs()

Давайте знову звернемося до функції abs (). Пригадайте, як у розд. 8 стандартні бібліотечні функції abs (), labs () і fabs () були згруповані в три перевантажені функції із загальним іменем myAbs(). Кожна з перевантажених версій функції myAbs() призначена для повернення абсолютного значення для даних "свого" типу. Незважаючи на те, що показане в розд. 8 перевантаження функції abs () можна вважати кроком уперед порівняно з використанням трьох різних бібліотечних функцій (з різними іменами), все ж таки це не кращий спосіб запису функції, яка повертає абсолютне значення заданого аргументу. Оскільки процедура повернення абсолютного значення числа однакова для всіх типів числових значень, то функція abs () може слугувати прекрасним прикладом для створення шаблонної функції. За наявності узагальненої версії функції abs () компілятор зможе автоматично створювати необхідну її версію. Програміст у цьому випадку звільняється від написання окремих версій для кожного типу даних[68].

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

Код програми 17.5. Демонстрація створення узагальненої версії функції myAbs()

#include <vcl>

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

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

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

 

template < class aType> aType myAbs(aType num)

{

return num < 0? -num: num;

}

 

int main ()

{

cout << myAbs(-10) << "\n"; // Для типу int

cout << myAbs(-10.0) << "\n"; // Для типу double

cout << myAbs(-10L) << "\n"; // Для типу long

cout << myAbs(-10.0F) << "\n"; // Для типу float

 

getch (); return 0;

}

Для вправи було б непогано, якби Ви спробували знайти інші бібліотечні функції-пре­тен­ден­ти для перероблення їх в узагальнені функції. Необхідно пам'ятати – головне тут те, що один і той самий алгоритм повинен бути застосований до широкого діапазону даних.

17.2. Узагальнені класи

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

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

Загальний формат оголошення узагальненого класу має такий вигляд:

template < class Ttype > class ім'я_класу {

.

.

.

}

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

Створивши узагальнений клас, можна створити його конкретний примірник, використовуючи такий загальний формат:

ім'я_класу < тип > ім'я_об'єкту;

У цьому записі елемент тип означає ім'я типу даних, які оброблятимуться примірником узагальненого класу. Функції-члени узагальненого класу автоматично є узагальненими. Тому програмісту не потрібно використовувати ключове слово template для безпосереднього визначення їх такими.

17.2.1. Створення класу з одним узагальненим типом даних

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

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

#include <vcl>

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

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

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

 

const int size=100;

 

// Створення узагальненого класу chergaClass.

template < class qType> class chergaClass { // Оголошення класового типу

qType qpMas[size];

int sloc, rloc;

public:

chergaClass() { sloc = rloc = 0;}

void putQp(qType izm);

qType getQp(); // Виведення з черги значення

};

 

// Занесення об'єкта в чергу.

template < class qType> void chergaClass<qType>::putQp(qType izm)

{

if (sloc==size){ cout << "Черга заповнена.\n"; return;}

sloc++;

qpMas[sloc] = izm;

}

 

// Вилучення об'єкта з черги.

template < class qType> qType chergaClass<qType>::getQp()




Поделиться с друзьями:


Дата добавления: 2014-11-29; Просмотров: 476; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.205 сек.