Міністерство освіти і науки України
Національний університет „Львівська політехніка”
Кафедра ЕОМ
/
Звіт
про виконання лабораторної роботи №8
з дисципліни:
“Програмування, частина 2 (Об’єктно-орієнтоване програмування)”
на тему:“ Шаблони”
2017
Мета: познайомитися із створенням шаблонів.
Теоретичні відомості
Шаблони являють собою схематичний опис побудови класів та функцій. Використовуючи шаблони, з'являється можливість створювати узагальнені специфікації для класів та функцій, що найчастіше носять назву параметризованих класів (generic classes) та параметризованих функцій (generic functions). Шаблони не прив’язані до конкретних типів даних і описують алгоритми, незалежно від типів даних. Дані алгоритми мають функціонувати однаково для різних типів даних. Такий опис дозволяє описати один раз функції, методи чи класи і на їх базі генерувати функції, методи і класи для кожного конкретного набору параметрів, що економить зусилля і час розробки програмного забезпечення. Після визначення загального шаблона, якщо для одного, кількох або всіх параметрів поведінка класу чи функції відрізнятиметься від описаної в загальному шаблоні, то створюється спеціалізація для конкретного набору параметрів. Спеціалізація може бути звичайною (неявною), явною або частковою.
Призначенням шаблонів є створення екземплярів (instantiating) шаблону, які вже є реальними функціями чи класами. При цьому відбувається прив'язування параметрів шаблону до даних визначеного типу. Цей процес називається конкретизацією. Cпроба компілятором створити екземпляр шаблону є генерацією програми. Тому зустрічаючи спробу створити екземпляр шаблону компілятор перемикається в режим його вивчення та запам'ятовування, а це - часові витирати.
Типи загального призначення, якими оперують шаблони, називаються шаблонними типами (template type), а їх сукупність параметрами шаблона (template parameters). Параметри шаблону як множина шаблонних типів може містити також преозначені і вбудовані типи С++.
Шаблонний тип Т є невизначеним узагальненим типом. По мірі використання шаблонів компілятор автоматично замінить тип Т іменем реального типу. Як правило, для імені шаблонного типу використовують ідентифікатори T чи Type. Проте це не обов'язково: ім'я можна декларувати будь-яким допустимим в С++ ідентифікатором. Шаблонний тип можна повноцінно використовувати в тілі шаблону, але це не є строгою вимогою.
Шаблон допускає використання параметрів, які ініціалізуються аргументами за замовчуванням, згідно з методологією оголошення і використання таких аргументів. Типи аргументів по замовчуванні можуть бути лише преозначеними або вбудованими. Використання шаблонних типів як аргументів по замовчуванню не допускається.
Під шаблон пам'ять не виділяється. Якщо екземпляр шаблону не створюється, то компілятор навіть не буде транслювати код шаблону. Це спричинює труднощі з використанням файлів заголовків, які містять лише оголошення шаблонів, а їх реалізація знаходиться у сpp-файлі. Для подолання цих недоліків треба підключати сpp-файл, а не файл заголовку, або код шаблону вносити у файл заголовку. Ранні версії С++ компіляторів не перевіряли синтаксис тіла незалежно від створення екземпляр шаблону. Сучасні компілятори відразу аналізують синтаксис коду тіла при першому знаходженні оголошення шаблону, а тому позбавлені цих недоліків.
Використання шаблонів може значно скоротити час створення програми. Це досягається тим, що з'являється можливість перенести незалежний від типу даних, один раз написаний і перевірений код спільний для множини різнорідних функцій в одну програмну конструкцію - шаблон.
Основним застереженням при роботі з шаблонами є правильне використання операцій до змінних шаблонних типів. Тобто усі операції, які використовувались до змінної шаблонної типу, повинні мати місце для того типу, яким буде заміщений даний шаблонний тип.
Таким чином, за допомогою реалізації узагальнених функцій можна зменшити розмір та складність програми. Особливо корисними шаблони є саме в бібліотеках класів – тут вони вказують програмісту необхідні специфікації, приховуючи при цьому деталі справжньої реалізації.
Параметризовані функції
Для виконання схожих операцій над різними типами даних часто використовуються перевантажені функції. Якщо ж для кожного типу даних повинні виконуватися ідентичні операції, то більш компактним і зручним рішенням є використання параметризованих (шаблонних) функцій. При цьому програміст повинен написати лише один опис шаблона функції. Базуючись на типах аргументів, використаних при виклику цієї функції, компілятор буде автоматично генерувати об'єктні коди функцій, що оброблятимуть кожен тип даних.
Параметризовані функції декларуються за допомогою ключового слова template. Це слово використовується для створення шаблону (каркасу), що в загальних рисах описує призначення функції та надає опис операцій – сутність алгоритму, що може застосовуватися до даних різних типів. Синтаксис оголошення функції-шаблону має вигляд:
temрlate <class T1, class T2, …, class Tn> тип ім'яФункції (аргументи)
{
// тіло функції
}
За ключовим словом template слідує не порожній список параметрів шаблону, який складається з ідентифікаторів типу T, кожному з яких передує ключове слово class або typename (згідно новішого стандарту). Коли компілятор створюватиме конкретну версію функції, то автоматично замінить параметри конкретними типами даних. Цей процес носить назву інстанціювання шаблону.
Синтаксис виклику шаблонної функції є таким самим як і виклик звичайної функції і має наступний вигляд:
ім'яФункції (аргументи);
Кожен формальний параметр з опису шаблона функції повинен з'явитися в списку параметрів функції принаймні один раз. Ім'я формального параметра може використовуватися в списку параметрів заголовка шаблона тільки один раз. Те ж ім'я формального параметра шаблона функції може використовуватися декількома шаблонами.
Шаблон функції може бути перевантажений, а саме можна визначити інші шаблони, що мають те ж ім'я функції, але різні набори параметрів. Також можна ввести не шаблонну функцію з тим же ім'ям та іншим набором параметрів функції.
Компілятор виконує процес узгодження, щоб визначити, який екземпляр функції відповідає конкретному викликові. Спочатку компілятор намагається знайти і використати функцію, що точно відповідає по імені та типам параметрів функції, що викликається. Якщо на цьому етапі компілятор зазнає невдачі, то він шукає шаблон функції, за допомогою якого він може згенерувати параметризовану функцію з точною відповідністю типів параметрів та імені функції; автоматичне перетворення типів не забезпечується. І як останню спробу, компілятор послідовно виконує процес підбору перевантаженої функції.
Спеціалізації шаблонних функцій
Варіант шаблонного класу чи шаблонної функції, які створюються компілятором в результаті конкретизації параметрів, називається спеціалізацією.
Існують 3 види спеціалізації – явна, неявна (повна), часткова спеціалізації:
Неявною спеціалізацією називається варіант шаблонної функції, що генерується в залежності від типу її аргументів.
//Шаблонна функція неявна спеціалізація
template <class T, class T2> bool func1(T v1, T2 v2)
{ return v1 == v2 ? true : false; };
Явною спеціалізацією називається варіант шаблонної функції, де всі параметри типу задані явним чином. Явна спеціалізація оголошується за допомогою наступної конструкції:
template <> типПовер Ім'яФункції<тип1,… типN>(аргументи)
{ /* тіло функції */ }
Наприклад
// Шаблонна функція явна спеціалізація
template <> bool func1<char*, char*>(char* pszStr1, char* pszStr2)
{return strcmp(pszStr1, pszStr2) ? false : true;};
Частковою спеціалізацією називається варіант шаблонної функції, де частина параметрів типу задана явним чином. Основна ідея часткової спеціалізації полягає в сумісному використанні шаблонних і наперед визначених типів.
// Шаблонна функція часткова спеціалізація
template <class T> bool func1(T v1, double v2)
{
if((double)(v2-v1)==0.0)
return true;
else
return false;
};
Виклик спеціалізації шаблонної функції як і звичайної шаблонної функції відбуваєтсья таки самим чином як і виклик звичайної нешаблонної функції.
Параметризовані класи
Визначаючи параметризований клас, ми створюємо його каркас (шаблон), що описує усі алгоритми, які використовуються класом. Фактичний тип даних, над яким проводитимуться маніпуляції, буде вказаний в якості параметру при конкретизації об'єктів цього класу. Компілятор автоматично згенерує відповідний об'єкт на основі вказаного типу. Іншими словами класи породжують об'єкти, а шаблонні класи - класи. Загальна форма декларування параметризованого класу така:
template < списокПараметрівШаблону > class ім’яКласу
{ // протокольна частина класу
}
Список параметрів шаблону може містити елементи двох видів:
специфікації формальних констант, що складаються з імені деякого типу з наступним ідентифікатором, наприклад, int i;
специфікації формальних типів, що складаються з ключового слова class або typename (згідно новішого стандарту), за яким слідує ідентифікатор; вони аналогічні параметрам шаблона функції, наприклад, class T.
Методи шаблонного класу автоматично стають шаблонами. Визначення методів, розташованих в тілі шаблона, нічим не відрізняються від визначення вбудованих методів звичайного класу. Визначення методів, розташовуваних поза тілом шаблона, має наступний вид:
template < списокПараметрівШаблону >
тип ім'яКласу <параметриШаблону>:: імяМетоду (аргументи)
{…}
Шаблонний клас і визначення його шаблонних методів мають знаходитися в одному файлі якщо тільки в компіляторі не визначено ключове слово export. Якщо дане слово визначене в компіляторі, то визначення методів шаблонного класу можна розміщати в інших файлах вживаючи перед визначенням тіла методу ключове слово export.
Щоб створити із шаблона екземпляр конкретного класу, потрібно оголосити об'єкт, вказавши для його типу ім'я шаблона з набором конкретних аргументів (типів і констант). Кожен формальний тип у списку параметрів шаблона потрібно замінити на ім'я конкретного типу. Кожна формальна константа заміняється на константу зазначеного в шаблоні типу. Після того, як представник шаблонного класу створений, з ним можна поводитись як і з будь-яким об'єктом, що належить до звичайного класу.
Створення екземпляру шаблонного класу може бути здійснено в явному і неявному вигляді.
Неявне створення екземпляру класу відбуваєтсья тоді, коли вперше знадобиться об’єкт даного класу. Наприклад:
Pair<char *, int> * ptr; /* оголошення вказівника. Екземпляр класу не генерується*/
Pair<char *, int> rating; /* неявне створення екземпляру класу і об’єкту на його базі*/
Явне створення екземпляру класу здійснюється компілятором завжди, коли він створюється явно з використанням ключового слова template. Наприклад:
template Pair<char *, int>;
Коли при обробці вихідного файлу компіляторові зустрічається створення об'єкта на основі деякого шаблона класу, він насамперед генерує представника шаблона, або шаблонний клас. Власне кажучи при цьому генеруються і компілюються коди усіх методів шаблона для даного набору його аргументів (і коди деяких допоміжних функцій). Після цього компілятор може конструювати об'єкт шаблонного класу, викликати потрібні методи об'єкта і т.д..
Якщо створюється шаблонний об'єкт, аргументи якого збігаються з аргументами об'єкта, раніше створеного в поточному модулі компіляції, то новий представник шаблона не генерується. Даний шаблонний клас вже існує, залишається тільки сконструювати об'єкт.
Для шаблона класу можна визначити шаблон дружньої функції. Такий шаблон буде породжувати окрему дружню функцію для кожного шаблонного класу, що буде генеруватися. Типовим прикладом шаблона дружньої функції може служити операція передачі об'єкта в потік.
Завдання:
Створити шаблон класу, який буде містити в собі масив частот, а його методи дозволятимуть працювати з цим масивом.
Код Train.h
#pragma once
#include <iostream>
template<class T>
class Trains {
private:
T* trainsArray;
int elementsCount;
public:
Trains(T* array = nullptr, int count = 0);
~Trains();
void setElementsCount(int count) { this->elementsCount = count; }
void settrainsArray(T* arr) { this->trainsArray = arr; }
int getElementsCount() { return this->elementsCount; }
T* gettrainsArray() { return this->trainsArray; }
T first() { return this->trainsArray[0]; }
T last() { return this->trainsArray[elementsCount - 1]; }
void change(int pos, T object) { this->trainsArray[pos] = object; }
void add(T elem);
void show();
};
template<class T>
inline Trains<T>::Trains(T * array, int count) {
this->trainsArray = array;
this->elementsCount = count;
}
template<class T>
inline Trains<T>::~Trains() {
delete[]trainsArray;
}
template<class T>
inline void Trains<T>::add(T elem) {
T* newArr = new T[elementsCount + 1];
for (int i = 0; i < this->elementsCount; i++) {
newArr[i] = this->trainsArray[i];
}
newArr[elementsCount] = elem;
this->elementsCount++;
this->trainsArray = newArr;
}
template<class T>
inline void Trains<T>::show() {
for (int i = 0; i < this->elementsCount; i++) {
cout << this->trainsArray[i] << " ";
}
}
Код main.cpp:
#include <iostream>
#include <string>
#include "Train.h"
using namespace std;
int main(void) {
cout << "Trains<int> intFlatsList: " << endl;
int arr1[] = { 1,2,3,4,5,6 };
int* ptr1 = arr1;
Trains<int> trainsArray1(ptr1, 6);
trainsArray1.show();
cout << endl;
trainsArray1.add(7);
trainsArray1.show();
cout << endl;
trainsArray1.change(4, 1);
trainsArray1.show();
cout << endl;
cout << "Trains<string> intFlatsList: " << endl;
string arr2[] = { "110","123","45","98" };
string* ptr2 = arr2;
Trains<string> trainsArray2(ptr2, 4);
trainsArray2.show();
cout << endl;
trainsArray2.add("455");
trainsArray2.show();
cout << endl;
trainsArray2.change(0, "1");
trainsArray2.show();
cout << endl;
}
Результат виконання програми:
/
Рис.1 - Ескіз екрана виконаної програми
Висновок: виконуючи дану лабораторну роботу я отримав знання та навички у вмінні створювати та використовувати шаблони. Ці навички стануть мені в пригоді при роботі над курсовою роботою.