Мы уже ранее рассматривали в С++ такой инструмент, как шаблоны, когда создавали . Почему стоит пользоваться шаблонами, было написано в статье, с шаблонами функций. Там мы рассмотрели основные положения шаблонов в С++. Давайте их вспомним.

Любой шаблон начинается со слова template , будь то шаблон функции или шаблон класса. После ключевого слова template идут угловые скобки — < > , в которых перечисляется список параметров шаблона. Каждому параметру должно предшествовать зарезервированное слово class или typename . Отсутствие этих ключевых слов будет расцениваться компилятором как . Некоторые примеры объявления шаблонов:

Template

Template

Template

Ключевое слово typename говорит о том, что в шаблоне будет использоваться встроенный тип данных, такой как: int , double , float , char и т. д. А ключевое слово class сообщает компилятору, что в шаблоне функции в качестве параметра будут использоваться пользовательские типы данных, то есть классы. Но не в коем случае не путайте параметр шаблона и шаблон класса. Если нам надо создать шаблон класса, с одним параметром типа int и char , шаблон класса будет выглядеть так:

Template

где T — это параметр шаблона класса, который может принимать любой из встроенных типов данных, то, что нам и нужно.

А если параметр шаблона класса должен пользовательского типа, например типа Array , где Array — это класс, описывающий массив, шаблон класса будет иметь следующий вид:

Template class Name { //тело шаблона класса };

C этим вам лучше разобраться изначально, чтобы потом не возникало никаких ошибок, даже, если шаблон класса написан правильно.

Давайте создадим шаблон класса Стек, где , в которой хранятся однотипные элементы данных. В стек можно помещать и извлекать данные. Добавляемый элемент в стек, помещается в вершину стека. Удаляются элементы стека, начиная с его вершины. В шаблоне класса Stack необходимо создать основные методы:

  • Push — добавить элемент в стек;
  • Pop — удалить элемент из стека
  • printStack — вывод стека на экран;

Итак реализуем эти три метода, в итоге получим самый простой класс, реализующий работу структуры стек. Не забываем про конструкторы и деструкторы. Смотрим код ниже.

#include "stdafx.h" #include template << "Заталкиваем элементы в стек: "; int ct = 0; while (ct++ != 5) { int temp; cin >> << "\nУдаляем два элемента из стека:\n"; myStack.pop(); // удаляем элемент из стека myStack.pop(); // удаляем элемент из стека myStack.printStack(); // вывод стека на экран return 0; } // конструктор template Stack::Stack(int s) { size = s > Stack bool Stack bool Stack void Stack::printStack() { for (int ix = size -1; ix >= 0; ix--) cout << "|" << setw(4) << stackPtr << endl; }

// код Code::Blocks

// код Dev-C++

#include using namespace std; #include template class Stack { private: T *stackPtr; // указатель на стек int size; // размер стека T top; // вершина стека public: Stack(int = 10);// по умолчанию размер стека равен 10 элементам ~Stack(); // деструктор bool push(const T); // поместить элемент в стек bool pop(); // удалить из стека элемент void printStack(); }; int main() { Stack myStack(5); // заполняем стек cout << "Заталкиваем элементы в стек: "; int ct = 0; while (ct++ != 5) { int temp; cin >> temp; myStack.push(temp); } myStack.printStack(); // вывод стека на экран cout << "\nУдаляем два элемента из стека:\n"; myStack.pop(); // удаляем элемент из стека myStack.pop(); // удаляем элемент из стека myStack.printStack(); // вывод стека на экран return 0; } // конструктор template Stack::Stack(int s) { size = s > 0 ? s: 10; // инициализировать размер стека stackPtr = new T; // выделить память под стек top = -1; // значение -1 говорит о том, что стек пуст } // деструктор template Stack::~Stack() { delete stackPtr; // удаляем стек } // элемент функция класса Stack для помещения элемента в стек // возвращаемое значение - true, операция успешно завершена // false, элемент в стек не добавлен template bool Stack::push(const T value) { if (top == size - 1) return false; // стек полон top++; stackPtr = value; // помещаем элемент в стек return true; // успешное выполнение операции } // элемент функция класса Stack для удаления элемента из стек // возвращаемое значение - true, операция успешно завершена // false, стек пуст template bool Stack::pop() { if (top == - 1) return false; // стек пуст stackPtr = 0; // удаляем элемент из стека top--; return true; // успешное выполнение операции } // вывод стека на экран template void Stack::printStack() { for (int ix = size -1; ix >= 0; ix--) cout << "|" << setw(4) << stackPtr << endl; }

Как видите шаблон класса Stack объявлен и определен в файле с main -функцией. Конечно же такой способ утилизации шаблонов никуда не годится, но для примера сойдет. В строках 7 — 20 объявлен интерфейс шаблона класса. Объявление класса выполняется привычным для нас образом, а перед классом находится объявление шаблона, в строке 7. При объявлении шаблона класса, всегда используйте такой синтаксис.

Строки 47 — 100 содержат элемент-функции шаблона класса Stack, причем перед каждой функцией необходимо объявлять шаблон, точно такой же, как и перед классом — template . То есть получается, элемент-функции шаблона класса, объявляются точно также, как и обычные шаблоны функций. Если бы мы описали реализацию методов внутри класса, то заголовок шаблона — template для каждой функции прописывать не надо.

Чтобы привязать каждую элемент-функцию к шаблону класса, как обычно используем бинарную операцию разрешения области действия — :: с именем шаблона класса — Stack . Что мы и сделали в строках 49, 58, 68, 83, 96.

Обратите внимание на объявление объекта myStack шаблона класса Stack в функции main , строка 24. В угловых скобочка необходимо явно указывать используемый тип данных, в шаблонах функций этого делать не нужно было. Далее в main запускаются некоторые функции, которые демонстрируют работу шаблона класса Stack . Результат работы программы смотрим ниже.

Заталкиваем элементы в стек: 12 3456 768 5 4564 |4564 | 5 | 768 |3456 | 12 Удаляем два элемента из стека: | 0 | 0 | 768 |3456 | 12

Собрался я было писать текст про всякие крутые структуры данных и тут оказалось, что мы ещё не разбирали несколько очень важных возможностей C++. Шаблоны - одна из них.

Шаблоны (templates) - очень мощное средство. Шаблонные функции и классы позволяют очень сильно упростить программисту жизнь и сберечь огромное количество времени, сил и нервов. Если вам покажется, что шаблоны не сильно-то и значимая тема для изучения, знайте - вы заблуждаетесь.

Шаблонные функции

Простой пример шаблонной функции:

код на языке c++ Type square (Type a) { Type b; b = a*a; return b; } int x = 5; int i; i = square(5); float y = 0.5; float f; f = square(y);

Если бы мы создавали функции по старинке, то тогда бы пришлось писать две разные функции: для типа int и для типа float. А если бы понадобилась такая же функция, использующая другие типы, пришлось бы заново писать и её. Используя шаблоны, можно ограничиться только одним экземпляром функции, оставив всю грязную работу компилятору.

Вместо использования какого-то определённого типа, в функции используется параметрический тип (или по другому - аргумент шаблона). Здесь я обозвал параметрический тип идентификатором Type. В функции этот идентификатор встречается три раза: возвращаемое значение, аргумент функции и определение переменной s. То есть Type используется как любой обычный тип.

Но чтобы код заработал, перед функцией нужно добавить следующую строку (я показал несколько вариантов синтаксиса, все они рабочие):

код на языке c++ template Type square (Type a) template < class Type > Type square (Type a) template < class Type > Type square (Type a)

Итак, перед функцией должно стоять ключевое слово template (шаблон), а в угловых скобках нужно указать имя параметрического типа с ключевым словом class. Вместо ключевого слова class можно использовать type - в общем-то никакой разницы.

Идентификатор параметрического типа тоже может быть любым. Мы часто будем пользоваться вот такими: TypeA, TypeB, Datatype, T.

Важное замечание : У шаблонных функций должен быть аргумент, чтобы компилятор мог определить какой именно тип использовать.

В шаблонах можно использовать несколько параметрических типов, и конечно же можно смешивать параметрические типы со стандартными (только нужно позаботиться о правильном приведении типов). Приведу пример в котором используется два параметрических типа TypeA, TypeB и базовый тип int:

код на языке c++ template TypeB example_function (TypeA a, TypeB b) { int x = 5; b = a + x; return b; }

Но шаблонные функции - не самое интересное, что мы сегодня рассмотрим.

Шаблонные классы

В общем-то шаблонные классы создаются почти так же как и шаблонные функции - перед именем класса записывается ключевое слово template. Шаблонные классы рассмотрим на примере стека:

код на языке c++ template class stack { private: int top; Type s; public: stack (): top(0) {} void push(Type var) { top++; s = var; } Type pop(); }; template Type stack::pop() { Type var = s; top--; return var; } Здесь мы опре

делили стек из десяти элементов. Эти элементы могут быть какого угодно типа, об этом чуть-чуть ниже.

Единственное на что хочу обратить ваше внимание: определение функций push и pop. Функция push определена внутри класса, а функция pop - снаружи. Для всех функции объявлённых за пределами класса, нужно обязательно указывать ключевое слово template. Выражение перед именем функции совпадает с тем, которое указывается перед именем класса.

Теперь посмотрим как работать с шаблонными классами:

код на языке c++ stack s1; stack s2; s1.push(3); s1.push(2); s1.pop(); s2.push(0.5);

При создании объекта, после имени класса нужно поставить угловые скобки, в которых указать нужный тип. После этого объекты используются так, как мы привыкли.

У шаблонных классов есть одна потрясающая особенность - кроме стандартных типов, они могут работать и с пользовательскими. Рассмотрим небольшой пример. Для этого определим простой класс warrior:

код на языке c++ class warrior { public: int health; warrior () : health(0) {} }; stack s; warrior w1; warrior w2; warrior w3; s.push(w1); s.push(w3); s.pop(); s.push(w2);

Смотрите, теперь в стеках можно размещать переменные типа warrior!!! Возможно вы не поверите мне, но это очень круто! Насколько это круто, вы сможете убедиться когда на основе списков мы будем создавать графы и деревья.

По шаблонам пока всё. Позже разберём более сложные случаи использования шаблонных классов.

Шаблоны функций, своими словами,- это инструкции, согласно которым создаются локальные версии шаблонированной функции для определенного набора параметров и типов данных.

На самом деле, шаблоны функций -это мощный инструмент в С++, который намного упрощает труд программиста. Например, нам нужно запрограммировать функцию, которая выводила бы на экран элементы массива. Задача не сложная! Но, чтобы написать такую функцию, мы должны знать тип данных массива, который будем выводить на экран. И тут нам говорят — тип данных не один, мы хотим, чтобы функция выводила массивы типа int , double , float и char .

Как оказалось, задача усложнилась. И теперь мы понимаем, что нам нужно запрограммировать целых 4 функции, которые выполняют одни и те же действия, но для различных типов данных. Так как мы еще не знакомы с шаблонами функций, мы поступим так: воспользуемся .

// перегрузка функции printArray для вывода массива на экран void printArray(const int * array, int count) { for (int ix = 0; ix < count; ix++) cout << array << " "; cout << endl; } void printArray(const double * array, int count) { for (int ix = 0; ix < count; ix++) cout << array << " "; cout << endl; } void printArray(const float * array, int count) { for (int ix = 0; ix < count; ix++) cout << array << " "; cout << endl; } void printArray(const char * array, int count) { for (int ix = 0; ix < count; ix++) cout << array << " "; cout << endl; }

Таким образом, мы имеем 4 перегруженные функции, для разных типов данных. Как видите, они отличаются только заголовком функции, тело у них абсолютно одинаковое. Я написал один раз тело функции для типа int и три раза его скопировал для других типов данных.

И, если запустить программу с этими функциями, то она будет исправно работать. Компилятор сам будет определять какую функцию использовать при вызове.

Как видите, кода получилось достаточно много, как для такой простой операции. А что если, нам понадобится запрограммировать в виде функции. Получается, что для каждого типа данных придется свою функцию создавать. То есть, сами понимаете, что один и тот же код будет в нескольких экземплярах, нам это ни к чему. Поэтому в С++ придуман такой механизм - шаблоны функций.

Мы создаем один шаблон, в котором описываем все типы данных. Таким образом исходник не будет захламляться никому ненужными строками кода. Ниже рассмотрим пример программы с шаблоном функции. Итак, вспомним условие: «запрограммировать функцию, которая выводила бы на экран элементы массива».

#include < count; ix++) cout << array << " "; cout << endl; } // конец шаблона функции printArray int main() { // размеры массивов const int iSize = 10, dSize = 7, fSize = 10, cSize = 5; // массивы разных типов данных int iArray = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; double dArray = {1.2345, 2.234, 3.57, 4.67876, 5.346, 6.1545, 7.7682}; float fArray = {1.34, 2.37, 3.23, 4.8, 5.879, 6.345, 73.434, 8.82, 9.33, 10.4}; char cArray = {"MARS"}; cout << "\t\t Шаблон функции вывода массива на экран\n\n"; // вызов локальной версии функции printArray для типа int через шаблон cout << "\nМассив типа int:\n"; printArray(iArray, iSize); // вызов локальной версии функции printArray для типа double через шаблон cout << "\nМассив типа double:\n"; printArray(dArray, dSize); // вызов локальной версии функции printArray для типа float через шаблон cout << "\nМассив типа float:\n"; printArray(fArray, fSize); // вызов локальной версии функции printArray для типа char через шаблон cout << "\nМассив типа char:\n";printArray(cArray, cSize); return 0; }

// код Code::Blocks

// код Dev-C++

#include #include using namespace std; // шаблон функции printArray template void printArray(const T * array, int count) { for (int ix = 0; ix < count; ix++) cout << array << " "; cout << endl; } // конец шаблона функции printArray int main() { // размеры массивов const int iSize = 10, dSize = 7, fSize = 10, cSize = 5; // массивы разных типов данных int iArray = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; double dArray = {1.2345, 2.234, 3.57, 4.67876, 5.346, 6.1545, 7.7682}; float fArray = {1.34, 2.37, 3.23, 4.8, 5.879, 6.345, 73.434, 8.82, 9.33, 10.4}; char cArray = {"MARS"}; cout << "\t\t Шаблон функции вывода массива на экран\n\n"; // вызов локальной версии функции printArray для типа int через шаблон cout << "\nМассив типа int:\n"; printArray(iArray, iSize); // вызов локальной версии функции printArray для типа double через шаблон cout << "\nМассив типа double:\n"; printArray(dArray, dSize); // вызов локальной версии функции printArray для типа float через шаблон cout << "\nМассив типа float:\n"; printArray(fArray, fSize); // вызов локальной версии функции printArray для типа char через шаблон cout << "\nМассив типа char:\n";printArray(cArray, cSize); return 0; }

Заметьте, код уменьшился в 4 раза, так как в программе объявлен всего один экземпляр функции - шаблон. В main я объявил несколько массивов - четыре, для типов данных: int , double , float , char . После чего, в строках 26, 28, 30, 32, выполняется вызов функции printArray для разных массивов. Результат работы программы показан ниже.

Шаблон функции вывода массива на экран Массив типа int: 1 2 3 4 5 6 7 8 9 10 Массив типа double: 1.2345 2.234 3.57 4.67876 5.346 6.1545 7.7682 Массив типа float: 1.34 2.37 3.23 4.8 5.879 6.345 73.434 8.82 9.33 10.4 Массив типа char: M A R S

Как видите программа корректно работает, и для этого нам понадобилось всего один раз определить функцию printArray в привычном для нас виде. Обратите внимание, что перед объявлением самой функции, в строке 5, стоит следующая запись template . Как раз эта запись и говорит о том, что функция printArray на самом деле является шаблоном функции, так как в первом параметре printArray стоит тип данных const T* , точно такой же как и в строке 5.

Все шаблоны функций начинаются со слова template , после которого идут угловые скобки, в которых перечисляется список параметров. Каждому параметру должно предшествовать зарезервированное слово class или typename .

Template

Template

Template

Ключевое слово typename говорит о том, что в шаблоне будет использоваться встроенный тип данных, такой как: int , double , float , char и т. д. А ключевое слово class сообщает компилятору, что в шаблоне функции в качестве параметра будут использоваться пользовательские типы данных, то есть классы.

У нас в шаблоне функции использовались встроенные типы данных, поэтому в строке 5 мы написали template . Вместо T можно подставить любое другое имя, какое только придумаете. Давайте подробно рассмотри фрагмент кода из верхней программы, я его вынесу отдельно.

// шаблон функции printArray template void printArray(const T * array, int count) { for (int ix = 0; ix < count; ix++) cout << array << " "; cout << endl; } // конец шаблона функции printArray

В строке 2 выполняется определение шаблона с одним параметром - T , причем этот параметр будет иметь один из встроенных типов данных, так как указано ключевое слово typename .

Ниже, в строках 3 — 8 объявлена функция, которая соответствует всем критериям объявления обычной функции, есть заголовок, есть тело функции, в заголовке есть имя и параметры функции, все как обычно. Но что эту функции превращает в шаблон функции, так это параметр с типом данных T , это единственная связь с шаблоном, объявленным ранее. Если бы мы написали

Void printArray(const int * array, int count) { for (int ix = 0; ix < count; ix++) cout << array << " "; cout << endl; }

то это была бы простая функция для массива типа int .

Так вот, по сути T - это даже не тип данных, это зарезервированное место под любой встроенный тип данных. То есть когда выполняется вызов этой функции, компилятор анализирует параметр шаблонированной функции и создает экземпляр для соответственного типа данных: int , char и так далее.

Поэтому следует понимать, что даже если объем кода меньше, то это не значит, что памяти программа будет потреблять меньше. Компилятор сам создает локальные копии функции-шаблона и соответственно памяти потребляется столько, как если бы вы сами написали все экземпляры функции, как в случае с перегрузкой.
Надеюсь основную мысль по шаблонам функций до вас довел. Для закрепления материала, давайте рассмотрим еще один пример программы, с использованием шаблона функции.

#include "stdafx.h" #include #include < size; ix++) if (max < array) max = array; return max; } int main() { // тестируем шаблон функции searchMax для массива типа char char array = "aodsiafgerkeio"; int len = strlen(array); cout << "Максимальный элемент массива типа char: " << searchMax(array, len) << endl; // тестируем шаблон функции searchMax для массива типа int int iArray = {3,5,7,2,9}; cout << "Максимальный элемент массива типа int: " << searchMax(iArray, 5) << endl; return 0; }

// код Code::Blocks

// код Dev-C++

#include #include using namespace std; // шаблон функции для поиска максимального значения в массиве template T searchMax(const T* array, int size) { T max = array; // максимальное значение в массиве for (int ix = 0; ix < size; ix++) if (max < array) max = array; return max; } int main() { // тестируем шаблон функции searchMax для массива типа char char array = "aodsiafgerkeio"; int len = strlen(array); cout << "Максимальный элемент массива типа char: " << searchMax(array, len) << endl; // тестируем шаблон функции searchMax для массива типа int int iArray = {3,5,7,2,9}; cout << "Максимальный элемент массива типа int: " << searchMax(iArray, 5) << endl; return 0; }

Вот вам еще один пример использования шаблонов функций. Шаблон функции объявлен в строках 5-13. Функция должна возвращать максимальное значение массива, поэтому возвращаемое значение типа T , ведь тип данных массива заранее не известен. Кстати внутри функции объявлена переменная max типа T , в ней будет храниться максимальное значение массива. Как видите, тип данных T используется не только для спецификации параметров функции, но и для указания типа возвращаемого значения, а также может свободно использоваться для объявления любых переменных внутри шаблона функции.

Максимальный элемент массива типа char: s Максимальный элемент массива типа int: 9

Шаблоны функций также можно перегружать другими шаблонами функций, изменив количество передаваемых параметров в функцию. Еще одной особенностью перегрузки является то, что шаблонные функции могут быть перегружены обычными не шаблонными функциями. То есть указывается то же самое имя функции, с теми же параметрами, но для определенного типа данных, и все будет корректно работать.

До настоящего момента обсуждались вопросы построения шаблонных классов на основе предопределенных (включенных в состав библиотеки классов) шаблонов классов. В этом разделе на простом примере обсуждается техника объявления собственных шаблонов классов и шаблонов функций.

Шаблонный класс обеспечивает стандартную реализацию дополнительной функциональности на основе ранее объявленных подстановочных классов.

Эта дополнительная функциональность может накладывать дополнительные ограничения на подстановочный класс . Например, для успешной работы объекта шаблонного класса подстановочный класс должен наследовать определенному интерфейсу. Иначе функциональность шаблонного класса просто невозможно будет реализовать.

Для формирования ограничений на подстановочные классы в C# используется механизм ограничителей параметров шаблона - он вводится при объявлении шаблона с помощью ключевого слова where , за которым могут располагаться имя параметра типа и список типов класса или интерфейса либо конструктор – ограничение new() :

Using System; using System.Collections; using System.Collections.Generic; namespace PatternArrays { //========== Это заголовок шаблона класса W ========== // Шаблон класса своими руками. T – параметр шаблона. // Шаблонный класс – это класс-шаблон, который детализируется // подстановочным классом. // При создании шаблонного класса вхождения параметра шаблона // (в данном случае это T) замещаются именем подстановочного // класса. Разработчик шаблона класса может выдвигать требования // относительно характеристик подстановочного класса. // Для этого используются специальные языковые конструкции, // называемые ОГРАНИЧИТЕЛЯМИ ПАРАМЕТРА ШАБЛОНА. // ОГРАНИЧИТЕЛЬ ПАРАМЕТРА ШАБЛОНА формулирует требования для // подстановочного класса. class W where T: IComparable, new() // Ограничитель параметра шаблона new() – особый // ограничитель. // Во-первых, в списке ограничителей шаблона // он всегда последний. // Во-вторых, этот ограничитель НЕ ограничивает. // Он ОБЕСПЕЧИВАЕТ обязательное выполнение явно // заданного конструктора умолчания для // подстановочного класса в шаблонном // классе. Это единственный способ заставить // выполниться конструктор умолчания // подстановочного класса при создании // объекта шаблонного класса. // Ограничитель подстановочного класса. // Шаблонный класс строится на основе шаблона и множества // подстановочных классов, которыми замещаются параметры // шаблона. Таким образом ограничители подстановочного // класса формулируют требования по поводу "родословной" // подстановочного класса. // В данном случае претендент на замещение параметра T // в шаблоне W обязательно должен наследовать интерфейс // IComparable. { // Вот место, которое предназначено объекту подстановочного класса. // Объект - представитель шаблонного класса включает объект, // представляющий подстановочный класс. public T t; // Конструктор шаблона. // Вот по какой схеме производится встраивание объекта-представителя // подстановочного класса. Всего лишь для того, чтобы эта схема // построения объекта - представителя шаблонного класса работала, // в объявлении шаблона должен присутствовать ограничитель параметра // шаблона new(). Его отсутствие приводит к возникновению ошибки // компиляции. С каких это пор необходимое требование стали // называть ограничением? public W() { t = new T(); } // Сравнение объектов в шаблоне. Обращение к функциям сравнения // регламентировано стандартными интерфейсами. // Полиморфизм через интерфейсы в действии. public int wCompare(T t) { return ((IComparable)this.t).CompareTo(t); } // А вот замечательный шаблон функции. // Он реализован в рамках класса-шаблона W. // Эта функция предназначена для формирования шаблонных очередей // из входных массивов объектов - представителей подстановочного // класса, представленного параметром шаблона Z. // Между прочим, такое обозначение параметра ничуть не хуже любого // другого. Более того, если бы здесь было использовано старое // обозначение параметра, транслятор выступил бы с предупреждением // по поводу того, что две разных сущности (параметр шаблона для // шаблона класса и параметр шаблона для параметра функции) // в рамках одного и того же объявления имеют одинаковые обозначения. public void QueueFormer(Queue queue, params Z values) { foreach (Z z in values) { queue.Enqueue(z); } } } //=============================================================== // Вот классы-кандидаты на подстановку в шаблон. // Первый класс подходит, а второй – не подходит! // Все решается на этапе трансляции. //=============================================================== class xPoints: IComparable { // Объект-генератор "случайных" чисел. static Random rnd = new Random(); public int x; public int y; public xPoints() { x = rnd.Next(0, 100); y = rnd.Next(0, 100); } // Ничто не может помешать классу иметь // различные версии конструкторов! public xPoints(int x, int y) { this.x = x; this.y = y; } // Вычисляется расстояние от начала координат. public int R { get { return (int)(Math.Sqrt(x * x + y * y)); } } // После реализации соответствующего интерфейса объект-КОМПАРЕР // обеспечивает реализацию алгоритма сравнения. public int CompareTo(object p) { return (this.R - ((xPoints)p).R); } } class yPoints { // Объект-генератор "случайных" чисел. static Random rnd = new Random(); public int x; public int y; public yPoints() { x = rnd.Next(0, 100); y = rnd.Next(0, 100); } // Шаблон функции в рамках объявления "обычного" класса. // Функция предназначена для формирования шаблонных магазинов // из входных массивов объектов - представителей подстановочного // класса, представленного параметром шаблона T. public void StackFormer(Stack stack, params T values) { foreach (T t in values) { stack.Push(t); } } } //============================================================== class Class1 { static void Main(string args) { W xw0 = new W(); W xw1 = new W(); // Объекты - представители шаблонного класса можно сравнивать // в результате // реализации интерфейса IComparable. if (xw0.wCompare(xw1.t) == 0) Console.WriteLine("Yes"); else Console.WriteLine("No"); // В силу ограничений параметра шаблона T, следующий код // для подстановочного // класса в принципе нереализуем.============================= //W yw0 = new W(); //W yw1 = new W(); //if (yw0.ww(yw1.t) == 0) Console.WriteLine("Yes"); //else Console.WriteLine("No"); //===================================================== // Демонстрация использования шаблона функции. // На основе подстановочного класса сформировали // шаблонную функцию для подстановочного класса, // которая обслуживает шаблонные очереди, формируемые на основе // предопределенного шаблона класса Queue<...>. Queue xpQueue = new Queue(); xw1.QueueFormer(xpQueue, new xPoints(0, 9), new xPoints(1, 8), new xPoints(2, 7), new xPoints(3, 6), new xPoints(4, 5), new xPoints(5, 4), new xPoints(6, 3), new xPoints(7, 2), new xPoints(8, 1), new xPoints(9, 0)); // Шаблоны классов и шаблоны функций концептуально не связаны. // В C# это самостоятельные и независимые конструкции. // Шаблон функции может быть объявлен где угодно - // в шаблоне класса и в рамках объявления "обычного" класса. // При объявлении шаблона функции ограничения на свойства // подстановочного класса, представленного параметром шаблона, // не упоминаются. // В силу ограничений на параметр шаблона класс yPoints в принципе // не может быть использован для построения шаблонного класса // на основе шаблона class W. // Однако этот же самый класс может быть использован для построения // шаблонной функции в шаблонном классе, созданном на основе // подстановочного класса xPoints! Queue ypQueue = new Queue(); xw1.QueueFormer(ypQueue, new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints(), new yPoints()); // А вот применение шаблона функции, объявленного в "обычном" классе. // Создали объект класса, содержащего шаблон функции // по обслуживанию очередей. yPoints yp = new yPoints(); // Ссоздали шаблонный стек и воспользовались шаблоном функции, // объявленной yPoints в классе. Stack xpStack = new Stack(); yp.StackFormer(xpStack, new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints(), new xPoints()); } } } Листинг 13.2.

Пример использования шаблонов: сортировка

Старая задача, новые решения с использованием предопределенных шаблонов классов и интерфейсов...

Using System; using System.Collections; using System.Collections.Generic; namespace PatternArrays { // Данные для массива элементов. // Подлежат сортировке в составе шаблонного массива методом Sort. class Points { public int x; public int y; public Points(int key1, int key2) { x = key1; y = key2; } // Вычисляется расстояние от начала координат. public int R { get { return (int)(Math.Sqrt(x * x + y * y)); } } } // ...ШАБЛОННЫЙ КОМПАРЕР на основе шаблона интерфейса... class myComparer: IComparer { // Предлагаемый ШАБЛОННЫЙ метод сравнения возвращает разность расстояний // двух точек (вычисляется по теореме Пифагора) от начала координат // – точки с координатами (0,0). Чем ближе точки к началу координат // – тем они меньше. Не требуется никаких явных приведений типа. // Шаблон настроен на работу с классом Points. int IComparerCompare(Points p1, Points p2) { return (p1.R - p2.R); } } // После реализации соответствующего ШАБЛОННОГО интерфейса // объект-КОМПАРЕР обеспечивает реализацию стандартного алгоритма // сортировки. class Class1 { static void Main(string args) { // Объект-генератор "случайных" чисел. Random rnd = new Random(); int i; // Очередь Points. QueueQP = new Queue(); // Шаблонный перечислитель. Предназначен для обслуживания // шаблонной очереди элементов класса Points. IEnumeratorEnP; // Сортировка поддерживается классом Array. Points pp; // Создали Компарер, способный сравнивать пары // объектов - представителей класса Points. myComparer c = new myComparer(); Console.WriteLine("========================================"); // Проинициализировали массив объектов - представителей класса Points. for (i = 0; i < 10; i++) { qP.Enqueue(new Points(rnd.Next(0, 10), rnd.Next(0, 10))); } enP = ((IEnumerable)(qP)).GetEnumerator(); for (i = 0; enP.MoveNext(); i++) { Console.WriteLine("{0}: {1},{2}", i, enP.Current.x, enP.Current.y); } // Сортируются элементы массива типа Points, который формируется на // основе шаблонной очереди. // Условием успешной сортировки элементов массива является // реализация интерфейса IComparer. Если Компарер не сумеет // справиться с поставленной задачей – будет возбуждено исключение. // На основе очереди построили массив. pp = qP.ToArray(); // А саму очередь можно почистить! qP.Clear(); try { Array.Sort(pp, c); } catch (Exception ex) { Console.WriteLine(ex); } // Сортировка произведена, очередь восстановлена. for (i = 0; i < 10; i++) qP.Enqueue(pp[i]); Console.WriteLine("========================================"); enP = ((IEnumerable)(qP)).GetEnumerator(); for (i = 0; enP.MoveNext(); i++) { Console.WriteLine("{0}: {1},{2}", i, enP.Current.x, enP.Current.y); } Console.WriteLine("========================================"); } } } Листинг 13.3.

Nullable-типы

Nullable-типы (простые Nullable-типы) представляют собой расширения простых типов. Их объявления принадлежат пространству имен System.Nullable .

Это шаблонные типы, то есть типы, построенные в результате детализации шаблонов. Шаблон Nullable<> используется для расширения простых типов, которые по своей сути являются структурами. Для обозначения Nullable шаблонных (построенных на основе шаблона) типов используются две нотации.

При создании функций иногда возникают ситуации, когда две функции выполняют одинаковую обработку, но работают с разными типами данных (например, одна использует параметры типа int, а другая типа float). Вы уже знаете из урока 13, что с помощью механизма перегрузки функций можно использовать одно и то же имя для функций, выполняющих разные действия и имеющих разные типы параметров. Однако, если функции возвращают значения разных типов, вам следует использовать для них уникальные имена (см. примечание к уроку 13). Предположим, например, что у вас есть функция с именем тах, которая возвращает максимальное из двух целых значений. Если позже вам потребуется подобная функция, которая возвращает максимальное из двух значений с плавающей точкой, вам следует определить другую функцию, например fmax. Из этого урока вы узнаете, как использовать шаблоны C++ для быстрого создания функций, возвращающих значения разных типов. К концу данного урока вы освоите следующие основные концепции:

  • Шаблон определяет набор операторов, с помощью которых ваши программы позже могут создать несколько функций.
  • Программы часто используют шаблоны функций для быстрого определения нескольких функций, которые с помощью одинаковых операторов работают с параметрами разных типов или имеют разные типы возвращаемых значений.
  • Шаблоны функций имеют специфичные имена, которые соответствуют имени функции, используемому вами в программе.
  • После того как ваша программа определила шаблон функции, она в дальнейшем может создать конкретную функцию, используя этот шаблон для задания прототипа, который включает имя данного шаблона, возвращаемое функцией значение и типы параметров.
  • В процессе компиляции компилятор C++ будет создавать в вашей программе функции с использованием типов, указанных в прототипах функций, которые ссылаются на имя шаблона.

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

СОЗДАНИЕ ПРОСТОГО ШАБЛОНА ФУНКЦИИ

Шаблон функции определяет типонезависимую функцию. С помощью такого шаблона ваши программы в дальнейшем могут определить конкретные функции с требуемыми типами. Например, ниже определен шаблон для функции с именем тах, которая возвращает большее из двух значений:

template Т mах(Т а, Т b)

{
if (а > b) return(а);
else return(b);
}

Буква T данном случае представляет собой общий тип шаблона. После определения шаблона внутри вашей программы вы объявляете прототипы функций для каждого требуемого вам типа. В случае шаблона тах следующие прототипы создают функции типа float и int.

float max(float, float);
int max(int, int);

Когда компилятор C++ встретит эти прототипы, то при построении функции он заменит тип шаблона T указанным вами типом. В случае с типом float функция тах после замены примет следующий вид:

template Т max(Т а, Т b)

{
if (a > b) return(а) ;
else return(b);
}

float max(float a, float b)

{
if (a > b) return(a) ;
else return(b);
}

Следующая программа МАХ_ТЕМР.СРР использует шаблон тах для создания функции типа int и float.

#include

template Т mах(Т а, Т b)

{
if (a > b) return(a);
else return(b);
}

float max(float, float);

int max(int, int);

{
cout << «Максимум 100 и 200 равен » << max(100, 200) << endl;
cout << «Максимум 5.4321 и 1.2345 равен » << max(5.4321, 1.2345) << endl;
}

В процессе компиляции компилятор C++ автоматически создает операторы для построения одной функции, работающей с типом int, и второй функции, работающей с типом float. Поскольку компилятор C++ управляет операторами, соответствующими функциям, которые вы создаете с помощью шаблонов, он позволяет вам использовать одинаковые имена для функций, которые возвращают значения разных типов. Вы не смогли бы это сделать, используя только перегрузкуфункций, как обсуждалось в уроке 13.

Использование шаблонов функций

По мере того как ваши программы становятся более сложными, возможны ситуации, когда вам потребуются подобные функции, выполняющие одни и те же операции, но с разными типами данных. Шаблон функции позволяет вашим программам определять общую, или типонезависимую, функцию. Когда программе требуется использовать функцию для определенного типа, например int или float, она указывает прототип функции, который использует имя шаблона функции и типы возвращаемого значения и параметров. В процессе компиляции C++ создаст соответствующую функцию. Создавая шаблоны, вы уменьшаете количество функций, которые должны кодировать самостоятельно, а ваши программы могут использовать одно и то же имя для функций,выполняющих определенную операцию, независимо от возвращаемого функцией значения и типов параметров.

ШАБЛОНЫ, КОТОРЫЕ ИСПОЛЬЗУЮТ НЕСКОЛЬКО ТИПОВ

Предыдущее определение шаблона для функции max использовало единственный общий тип Т. Очень часто в шаблоне функции требуется указать несколько типов. Например, следующие операторы создают шаблон для функции show_array, которая выводит элементы массива. Шаблон использует тип Т для определения типа массива и тип Т1 для указания типа параметра count:

template

{
T1 index;
for (index =0; index < count; index++) cout << array << ‘ ‘;
cout << endl;
}

Как и ранее, программа должна указать прототипы функций для требуемых типов:

void show_array(int *, int);
void show_array(float *, unsigned);

Следующая программа SHOW_TEM.CPP использует шаблон для создания функций, которые выводят массивы типа int и типа float.

#include

template void show_array(T *array,T1 count)

{
T1 index;
for (index =0; index < count; index++) cout << array “ ‘ ‘;
cout << endl;
}

void show_array(int *, int);

void show_array(float *, unsigned);

{
int pages = { 100, 200, 300, 400, 500 };
float pricesH = { 10.05, 20.10, 30.15 };
show_array(pages, 5);
show_array(prices, 3);
}

Шаблоны и несколько типов

По мере того как шаблоны функций становятся более сложными, они могут обеспечить поддержку нескольких типов. Например, ваша программа может создать шаблон для функции с именем array_sort, которая сортирует элементы массива. В данном случае функция может использовать два параметра: первый, соответствующий массиву, и второй, соответствующий количеству элементов массива. Если программа предполагает, что массив никогда не будет содержать более 32767 значений она может использовать тип int для параметра размера массива. Однако более универсальный шаблон мог бы предоставить программе возможность указать свой собственный тип этого параметра, как показано ниже:

template void array_sort(T array, T1 elements)

{
// операторы
}

С помощью шаблона array_sort программа может создать функции которые сортируют маленькие массивы типа float (менее 128 элементов) и очень большие массивы типа int, используя следующие прототипы:

void array_sort(float, char);
void array_sort(int, long);

ЧТО ВАМ НЕОБХОДИМО ЗНАТЬ

Как вы уже знаете, использование шаблонов функций уменьшает объем программирования, позволяя компилятору C++ генерировать операторы для функций, которые отличаются только типами возвращаемых значений и параметров. Из урока 30 вы узнаете, как использовать шаблоны для создания типонезависимых, или общих, классов. До изучения урока 30 убедитесь, что вы освоили следующие основные концепции:

  1. Шаблоны функций позволяют вам объявлять типонезависимые, или общие, функции.
  2. Когда вашей программе требуется использовать функцию с определенными типами данных, она должна указать прототип функции, который определяет требуемые типы.
  3. Когда компилятор C++ встретит такой прототип функции, он создаст операторы, соответствующие этой функции, подставляя требуемые типы.
  4. Ваши программы должны создавать шаблоны для общих функций, которые работают с отличающимися типами. Другими словами, если вы используете с какой-либо функцией только один тип, нет необходимости применять шаблон.
  5. Если функция требует несколько типов, шаблон просто назначает каждому типу уникальный идентификатор, например Т, T1 и Т2. Позже в процессе компиляции компилятор C++ корректно назначит типы, указанные вами в прототипе функции.