Можно определить несколько типов-шаблонов данных в инструкции template, используя список с запятыми в качестве разделителя. Например, следующая программа создает функцию-шаблон, имеющую два параметра типа:
#include <iostream.h>
template <class type1, class type2> void F(type1 &x, type2 &y)
{
cout << x << ' ' << у << endl;
}
int main()
{
F(10, "hi"); F(0.23, ‘q’);
return 0;
}
В этом примере типы-шаблоны type1 и type2 заменяются компилятором на типы int, char*, double и char соответственно, причем компилятор сгенерирует два различных экземпляра функции F(). Следует иметь в виду, что когда создается функция-шаблон, то по существу этим компилятору разрешается создавать столько различных версий этой функции, сколько необходимо, чтобы обрабатывать те типы данных, которые передаются этой функции при ее вызове.
Функции-шаблоны сходны с перегруженными функциями, за исключением того, что они более ограниченные. Для перегруженных функций можно выполнять различные действия в теле каждой функции. В отличие от этого, для функции-шаблона необходимо выполнять одни и те же общие действия, и только тип данных может быть различным. Если перегруженные функции выполняют различные действия, то они не могут быть заменены функцией-шаблоном.
Другим ограничением на функции-шаблоны является то, что виртуальная функция не может быть функцией-шаблоном.
Кроме функций-шаблонов можно также определить классы-шаблоны. Для этого следует создать класс, определяющий все алгоритмы, но фактический тип данных является параметром, определяющимся при создании класса.
Классы-шаблоны полезны тогда, когда класс содержит логику, допускающую значительные обобщения. Например, алгоритм для обработки очереди целых чисел также будет работать с очередью символов. Аналогично механизм, поддерживающий связанный список почтовых адресов, также может поддерживать связанный список сведений об автомобилях. Используя классы-шаблоны, можно создавать классы, поддерживающие очереди, связанные списки, массивы и т. д. для произвольных типов данных. Компилятор автоматически создаст корректный код, основываясь на типе данных, указанном перед компиляцией. Общая форма объявления класса-шаблона показана ниже:
template <class тип1, class тип2, ...> class имя_класса
{
// тело класса
};
Здесь тип1, тип2, и т.д.являются параметрами-типами, которые будут указаны при создании экземпляра класса.
После создания класса-шаблона можно создать конкретный экземпляр этого класса, используя следующую общую форму:
имя_класса<тип> объект;
Здесь тип является именем типа данных, с которыми будет оперировать данный класс.
функции-члены класса-шаблона являются сами по себе автоматически шаблонами. Нет необходимости особым образом указывать на то, что они являются шаблонами, с использованием ключевого слова template.
В следующей программе создается класс-шаблон stack, реализующий стандартный стек «последним вошел - первым вышел». Он может использоваться для реализации стека с произвольным типом данных. В представленном здесь примере создаются стеки символов, целых чисел и чисел с плавающей точкой.
#include <iostream.h>
const int SIZE = 100;
template<class X> class stack
{
X st[SIZE];
int top;
public:
stack() { tos = 0; cout << "Stack initialized\n"; }
Как можно видеть, объявление класса-шаблона подобно объявлению функции-шаблона. Тип-шаблон используется при объявлении класса и реализации его функций-членов. При объявлении конкретного экземпляра класса stack компилятор автоматически генерирует все функции и данные, необходимые для обработки фактических данных. В этом примере объявляются три различных типа стеков (один для целых чисел, другой для вещественных и третий для символов). Рассмотрим следующее объявление:
stack<int> a; // создание целочисленного стека
stack<double> b; // создание вещественного стека
stack<char> с; // создание символьного стека
Обратим внимание, каким образом нужный тип данных подставляется в угловые скобки. Изменяя тип данных, указываемый при создании объектов класса stack, одновременно изменяется тип данных, хранящихся в стеке. Например, можно создать другой стек, хранящий указатели на символы:
stack<char*> char_ptr_stack;
Также можно создать стек, содержащий определенный тип данных. Например, можно хранить адреса, используя структуру:
struct addr
{
char name[40];
char street[40];
char city[30];
char state[3];
char zip[12]; }
Далее можно использовать класс stack для создания стека, в котором хранятся объекты типа addr:
stack<addr> obj;
Класс-шаблон может иметь несколько типов в качестве параметров шаблона. Достаточно объявить все типы-шаблоны в виде списка, разделенного запятыми. Например, в следующем коротком примере создается класс, использующий два типа-шаблона:
#include <iostream.h>
template <class Type1, class Type2> class myclass
{
Type1 i;
Type2 j;
public:
myclass(Type1 a, Type2 b) { i = a; j = b; }
void show() { cout << i << “ “ << j << “\n”; }
};
int main()
{
myclass<int, double> o1(10, 0.23);
myclass<char, char*> o2('X', "This is a test");
o1.show(); // вывод int, double
o2.show(); // вывод char, char*
return 0;
}
Эта программа выдаст следующий результат на экран:
10 0.23
X This is a test
В программе объявляются два типа объектов. Объект o1 использует типы данных int и double. Объект o2 использует типы данных char и char*. В обоих случаях компилятор автоматически генерирует необходимые данные и функции в соответствии с типом данных, передаваемых конструктору в качестве аргументов.
Функции-шаблоны и классы-шаблоны обеспечивают беспрецедентные возможности для создания многократно используемого кода. Если процедура имеет общий характер, то стоит рассмотреть возможность ее реализации в виде шаблона. После того как этот шаблон будет отлажен и проверен, можно использовать его снова и снова в различных ситуациях, не внося никаких изменений. Однако не надо поддаваться искушению реализовать все в виде функций-шаблонов и классов-шаблонов. Неуместное использование шаблонов может сильно запутать.