Общая форма объявления шаблона класса следующая:
Template <class Type>
Class имя_класса
{
тело класса;
}
В теле класса может участвовать тип Type, а перед определением класса, как для шаблонов функции, в угловых скобках указывается название этого типа. В угловых скобках можно указать список типов, разделенных запятыми. В теле класса названия указанных типов можно использовать в любом месте. Конкретная реализация определенного таким образом класса создается с помощью следующей общей формы:
имя_класса < тип > объект;
где тип – тип переменной, которая будет параметром класса.
Пример. Определим параметризованный контейнерный класс — массив. Этот массив защищен в том смысле, что при записи и чтении его элементов контролируется выход за границы массива. Такой массив называется ограниченным.
#include <conio.h>
#include <iostream.h> //библиотека потокового ввода-вывода
#include <stdlib.h> //стандартная библиотека
template <class Atype> class array
{
Atype *a; // элементы массива
int length; // число элементов
public:
array(int size); //конструктор
~array() {delete [] a;} //деструктор
Atype& operator[] (int i); //получение элемента массива
};
template <class Atype>
array <Atype>:: array (int size) // конструктор
{
int i; length = size;
a = new Atype[size]; // выделение памяти
if(!a) { cout << "nнет памяти для массива";
exit(1);
}
for(i=0; i<size; i++) a[i] = 0; // запись нулей
}
template <class Atype>
Atype& array <Atype>:: operator[](int i)
{
if(i<0 || i > length-1)
{
cout << "nзначение с индексом " << i;
cout << " выходит за пределы массива";
exit(1);
}
return a[i];
}
main()
{
array <int> ix(20); //массив целых чисел
array <double> dx(20); // массив чисел с плавающей точкой
int i;
clrscr();
for(i=0; i < 20; i++) ix[i] = i;
cout << "nмассив целых чисел" << ":n";
for(i=0; i < 20; i++) cout << ix[i] << " ";
for(i=0; i < 20; i++) dx[i] = (double) i;
cout << "nмассив чисел с плавающей точкой: n";
for(i=0; i < 20; i++) cout << dx[i] << " ";
ix[20] = 1; // генерирует ошибку
return 0;
}
Результат работы программы
массив целых чисел:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
массив чисел с плавающей точкой:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
значение с индексом 20 выходит за пределы массива
Правила определения шаблонов классов:
· шаблоны классов не могут быть вложены в другие классы;
· шаблоны классов могут иметь нетипизированные параметры; значения, указанные для этих параметров, должны быть константами.
Например, определим параметризованный класс стека. В данном примере иллюстрируется преимущество использования нетипизированных параметров.
template <class T, int size> // здесь size нетипизированный параметр
class
{
T v[size];
int top;
public:
stack(): top(-1){}
~stack() {}
void Push(const T& x)
{
if(top < size-1) {v[++top] = x;}
}
T& Pop() {if (top > -1) return v[top—];}
};
main()
{
stack <int, 20> tiny;
stack <int, 1000> huge;
tiny.Push(-25);
}
Параметр size используется для создания полей массива v[] без применения операции new, которая может быть выполнена неудачно. При таком определении типы stack <int, 20> и stack <int, 1000> будут различными типами. В частности, оператор объявления
stack <int, 20> *s = &tiny;
будет верным, а оператор
stack <int, 1000> *s = &tiny;
будет ошибочным.
· Шаблоны для определенных типов могут быть переопределены для того, чтобы выполнять (или не выполнять) какие-либо действия.
Поясним на примере. Пусть определён класс стека:
template <class T>
class Stack
{
T *v;
int size, top;
public:
stack (int n); // n – размер стека
~stack();
void Push (const T&); // записать Т в стек
T& Pop(); // извлечь Т из стека
…
}
Переопределим его для T = char* :
class Stack <char *>
{
char ** v; // указатель на char*
int size, top;
public:
Stack(int n);
~stack();
void Push(const char*&);
char* Pop();
…
// далее следуют новые функции Push и Pop
};
· Шаблоны классов могут быть использованы структурами или объединениями, например:
Template <class T> struct S {T *x; …}
· Статические члены параметризованного класса являются общими для каждого конкретного экземпляра этого класса.
· Шаблоны составных функций класса определяются вне класса, при помощи описания template. Например, определим для класса стека функцию Push:
Template <class T>
Void stack <T>:: Push(const T& element)
{
if(top == size-1) error(“stack overflow”);
else v[++top] = element;
}
· Шаблоны составных функций могут быть переопределены для отдельных типов. Например:
Void Stack <char > :: Push(const char& P)
{
…
}