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

Void swapAb(aType &а, аТуре &b)

{

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

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

}

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

      1. Безпосередньо задане перевизначення узагальненої функції

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

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

Код програми 7.2. Демонстрація механізму перевизначення шаблонної функції #include <vcl>

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

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

{

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

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

cout«"Виконується шаблонна функція swapAB" « endl;

}

// Ця функція перевизначає узагальнену версію функції swapABO для int-параметрів, void swapAB(int &а, int &b)

{

int tmp;

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

cout«"Це int-спеціалізація функції swapAB" « endl;

}

Int mainO

{

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

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

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

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

swapAB(i, j); // Викликається безпосередньо перевизначена функція swapAB().

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

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

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

getchO; return 0;

}

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

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

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

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

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

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

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

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

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

// Використання нового синтаксису задавання спеціалізації templateo void swapAB<int>(int &а, int &b)

{

int tmp;

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

cout«"Це int-спеціалізація функції swapAB" « endl;

}

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

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

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

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

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

#include <vcl>

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

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

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

template <class аТуре, class ЬТуре> void FunC(aType а, ЬТуре b)

{

cout« а «"" « b « endl;

}

int mainO

{

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

FunC(0.23,10L);

getchO; return 0;

}

У наведеному прикладі у процесі виконання функції mainO, коли компілятор генерує конкретні примірники функції FunCO, заповнювачі типів аТуре і ЬТуре замі­нюються спочатку парою типів даних int і char *, а потім парою double і long відпо­відно.

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

      1. Механізм перевнзначення специфікації шаблону функції

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

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

#include <vcl>

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

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

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

// Перша версія шаблону FunO template <class аТуре> void Fun(aType a)

{

cout«"Виконується функція Fun(aType а)" « endl;

}

// Друга версія шаблону FunO-

template <class aType, class bType> void Fun(aType a, bType b)

{

cout«"Виконується функція Fun(aType a, bType b)" « endl;

}

int mainO

{

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

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

getchO; return 0;

}

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

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

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

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

#include <vcl>

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

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

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

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

template<class аТуре> void repeat(aType data, int times)

{

int tim = times; do {

cout«tim - times +1 «" ==>"« data « endl; times-;

} while(times); cout« endl;

}

int mainO

{

repeat("L(e тест.", 3); repeat(100, 5); repeat(99.0/2,4);

getchO; return 0;

}

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

  1. ==> Це тест.

  2. ==> Це тест.

  3. ==> Це тест.

1 ==>100

  1. ==>100

  2. ==>100

  3. ==>100

  4. ==>100

  1. ==>49.5

  2. ==>49.5

  3. ==>49.5

  4. ==>49.5

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

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

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

void outdata(int і)

{

cout« і « endl;

}

void outdata(double d)

{

cout« d* 3.1416 « endl;

}

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

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

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

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

#include <vcl>

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

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

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

template <class аТуре> аТуре myAbs(aType п)

{

return п < 0 ? -n : п;

}

int mainO

{

cout« myAbs(-10) « endl; // Для типу int

cout« myAbs(-10.0) « endl; // Для типу double cout« myAbs(-IOL) « endl; // Для типу long

cout« myAbs(-IO.OF) « endl; // Для типу float

getchO; return 0;

}

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

    1. Поняття про узагальнені класи

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

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

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

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

  1. Тіло класу

};

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

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

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

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

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

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

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

#include <vcl>

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

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

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

const int size=100;

// Створення узагальненого класу queueClass. template <class qType> class queueClass { qType array[size]; int sloe, rloc; public:

queueClass() {sloe = rloc = 0;} void Get(qType c);

qType Put(); // Виведення з об'єкта значення

};

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

template <class qType> void queueClass<qType>::Get(qType c)

{

if(sloc==size) {cout«"Черга заповнена" « endl; return;} sloc++;

array[sloc] = c;

}

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

template <class qType> qType queueClass<qType>::PutO

{

if(rloc == sloe) {cout «"Черга порожня" « endl; return 0;} rloc++;

return array[rloc];

}

int mainO

{

queueClass<int> ObjA, ObjB; // Створюємо дві черги для int-значень.

ObjA.Get(IO);

ObjB.Get(19);

ObjA.Get(20);

ObjB.Get(1);

cout« ObjA.PutO «""; cout« ObjA.PutO <<" cout« ObjB.PutO «""; cout« ObjB.PutQ << endl;

queueClass<double> ObjC, ObjD; // Створюємо дві черги для double-значень ObjC. Get(10.12);

ObjD.Get(19.99);

ObjC.Get(-20.0);

ObjD.Get(0.986);

cout« ObjC.Put() «""; cout« ObjC.Put() «""; cout« ObjD.Put() «""; cout« ObjD.Put() « endl;

getchO; return 0;

}

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

1020 19 1

10.12-20 19.99 0.986

У цьому коді програми оголошення узагальненого класу є подібним до ого­лошення узагальненої функції. Тип даних, який зберігаються в черзі, узагальне­ний в оголошенні класу. Він невідомий доти, доки не буде оголошений об'єкт кла­су queueClass, який і визначить реальний тип даних. Після оголошення конкретно­го примірника класу queueClass компілятор автоматично згенерує всі функції та змінні, необхідні для оброблення реальних даних. У цьому прикладі оголошують­ся два різних типи черги: дві черги для зберігання цілих чисел і дві черги для зна­чень типу double. Зверніть особливу увагу на ці оголошення: queueClass<int> ObjA, ObjB; queueClass<double> ObjC, ObjD;

Зауважте, як вказується потрібний тип даних: він поміщається в кутові дуж­ки. Змінюючи тип даних при створенні об'єктів класу queueClass, можна змінити тип даних, який зберігаються в черзі. Наприклад, використовуючи таке оголошен­ня, можна створити ще одну чергу, яка міститиме покажчики на символи: queueClass<char*> chrptrQ;

Можна також створювати черги для зберігання даних, тип яких створений програмістом. Наприклад, нехай необхідно використати таку структуру для збері­гання інформації про адресу:

struct addrStruct { // Оголошення типу структури char name[40]; char street[40]; char city[30]; char state[3]; char zip[12];

};

Тепер для того, щоби за допомогою узагальненого класу queueClass, можна було згенерувати чергу для зберігання об'єктів типу addrStruct, достатньо використовува­ти таке оголошення:

queueClass<addrStruct> obj;

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

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

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

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

#include <vcl>

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

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

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

template <class аТуре, class ЬТуре> class myClass { аТуре с;

ЬТуре d; public:

myClass(aType ObjA, ЬТуре ObjB) {c = ObjA; d = ObjB;} void showType()

{cout«"c=" « c «d=" « d « endl;}

};

int mainO

{

myClass<int, double> ObjA(10,0.23); myClass<char, char*> ObjBfx', "Це тест.");

ObjA.showTypeO; // Відображення int- і double-значень ObjB.showType (); // Відображення значень типу char і char *

getchO; return 0;

}

Внаслідок виконання ця програма відображає на екрані такі результати: с= 10; d= 0.23 с= х; d= Це тест.

У цьому коді програми оголошується два види об'єктів. Об'єкт ObjA викорис­товує дані типу int і double, а об'єкт ObjB - символ і покажчик на символ. Для цих ситуацій компілятор автоматично генерує дані та функції відповідного способу побудови об'єктів.