На этом шаге мы рассмотрим описание и использование шаблонов классов.
Напомним, что шаблон семейства функций определяет потенциально неограниченное множество родственных функций. Он имеет следующий вид:
template <список_параметров_шаблона> определение_функции
Здесь угловые скобки являются неотъемлемым элементом определения. Список параметров шаблона должен быть заключен именно в угловые скобки.
Аналогично определяется шаблон семейства классов:
template <список_параметров_шаблона> определение_класса
Шаблон семейства классов определяет способ построения отдельных классов подобно тому, как класс определяет правила построения и формат отдельных объектов. В определении класса, входящего в шаблон, особую роль играет имя класса. Оно является не именем отдельного класса, а параметризованным именем семейства классов.
Как уже отмечалось в связи с шаблонами функций, определение шаблона может быть только глобальным.
В качестве примера рассмотрим векторный класс (в число данных входит одномерный массив). Какой бы тип ни имели элементы массива (целый, вещественный, с двойной точностью и т.д.), в этом классе должны быть определены одни и те же базовые операции, например доступ к элементу по индексу и т.д. Если тип элементов вектора задавать как параметр шаблона класса, то система будет формировать вектор нужного типа (и соответствующий класс) при каждом определении конкретного объекта.
Следующий шаблон позволяет автоматически формировать классы векторов с указанными свойствами:
//TEMPLATE.VEC - шаблон векторов. template <class T> // Т - параметр шаблона. class Vector { T *data; // Начало одномерного массива. int size; // Количество элементов в массиве public: Vector(int); // Конструктор класса vector. ~Vector() { delete[] data; } // Деструктор. // Расширение действия (перегрузка) операции "[]": T& operator[] (int i) { return data[i]; } }; // Внешнее определение конструктора класса: template <class T> Vector <T>::Vector(int n) { data = new T[n]; size = n; };
Когда шаблон введен, у программиста появляется возможность определять конкретные объекты конкретных классов, каждый из которых параметрически порожден из шаблона. Формат определения объекта одного из классов, порождаемых шаблоном классов:
имя_параматризованного_класса <фактические_параметры_шаблона> имя_объекта(параметры_кокструктора);
В нашем случае определить вектор, имеющий восемь вещественных координат типа double, можно следующим образом:
Vector <double> Z(8);
Проиллюстрируем сказанное следующей программой:
//OOР37_1.СРР - формирование классов с помощью шаблона. #include "template.vec" // Шаблон классов "вектор". #include <iostream.h> main () { // Создаем объект класса "целочисленный вектор": Vector <int> X(5); // Создаем объект класса "символьный вектор": Vector <char> C (5) ; // Определяем компоненты векторов: for (int i = 0; i < 5; i++) { X[i] = i; C[i] = 'A' + i; } for (i = 0; i < 5; i++) cout <<" " << X[i] <<' ' << C[i]; }
Результат выполнения программы:
0 A 1 B 2 С 3 D 4 E
В программе шаблон семейства классов с общим именем Vector используется для формирования двух классов с массивами целого и символьного типов. В соответствии с требованием синтаксиса имя параметризованного класса, определенное в шаблоне (в примере Vector), используется в программе только с последующим конкретным фактическим параметром (аргументом), заключенным в угловые скобки. Параметром может быть имя стандартного или определенного пользователем типа. В данном примере использованы стандартные типы int и char. Использовать имя Vector без указания фактического параметра шаблона нельзя - никакое умалчиваемое значение при этом не предусматривается.
В списке параметров шаблона могут присутствовать формальные параметры, не определяющие тип, точнее - это параметры, для которых тип фиксирован:
//OOР37_2.СРР #include <iostream.h> template <class T, int size = 64> class row { T* data; int length; public: row() { length = size; data = new T[size]; } ~row() { delete data; } T& operator [] (int i) { return data[i]; } }; void main() { row <float,8> rf; row <int,8> ri; for (int i = 0; i < 8; i++) { rf[i] = i; ri[i] = i * i; } for (i = 0; i < 8; i++) cout << " " << rf[i] << ' ' << ri[i]; }
Результат выполнения программы:
0 0 1 1 2 4 3 9 4 16 5 25 6 36 7 49
В качестве аргумента, заменяющего при обращении к шаблону параметр size, взята константа. В общем случае может быть использовано константное выражение, однако выражения, содержащие переменные, использовать в качестве фактических параметров шаблонов нельзя.