Объектно-ориентированное программирование - Учебное пособие (А.А. Хусаинов)

3.1. шаблоны классов

 

Общая форма объявления шаблона класса следующая:

 

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 << " нет памяти для массива";

                 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 << " значение с индексом " << 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 << " массив целых чисел" << ": ";

   for(i=0; i < 20; i++) cout << ix[i] << " ";

   for(i=0; i < 20; i++) dx[i] = (double) i;

   cout << " массив чисел с плавающей точкой: ";

   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)

{

   …

}