Шаблоны

Автор: | 07.10.2018

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

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

Допустим, у нас есть функция, которой мы часто пользуемся, например, функция пузырьковой сортировки по возрастанию:

void BubbleSortUp(int & a[]){
   for(int i=ArraySize(a)-1;i>0;i--){
      for(int j=0;j<i;j++){
         if(a[j]>a[j+1]){
            int tmp=a[j];
            a[j]=a[j+1];
            a[j+1]=tmp;
         }
      }
   }
}

Как видим, функция работает с массивами типа int, однако, массивы int – это не единственный тип массивов, которые мы можем использовать, так же может потребоваться отсортировать массив типа double или long и т.п.

Конечно, можно сделать копию функцию с точно таким же именем (перегрузить имя) но для другого типа, например для типа long:

void BubbleSortUp(long & a[]){
   for(int i=ArraySize(a)-1;i>0;i--){
      for(int j=0;j<i;j++){
         if(a[j]>a[j+1]){
            long tmp=a[j];
            a[j]=a[j+1];
            a[j+1]=tmp;
         }
      }
   }
}

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

Для создания шаблона определяемся какой тип в функции должен быть универсальным. Если взять первую функцию из это статьи, то для нее этим типом будет int. Обычно в шаблонах для указания универсального типа используется буква T (можно и любую другую букву или их сочетание).   Сначала пишем заголовок шаблона в котором указываем имя типа, а затем пишем функцию в которой вместо стандартного типа будет указан универсальный:

template <typename T>
void BubbleSortUp(T & a[]){
   for(int i=ArraySize(a)-1;i>0;i--){
      for(int j=0;j<i;j++){
         if(a[j]>a[j+1]){
            T tmp=a[j];
            a[j]=a[j+1];
            a[j+1]=tmp;
         }
      }
   }
}

Имейте ввиду, что в языке есть стандартная функция ArraySort(), она работает с использованием более быстрого алгоритма, нежели пузырьковая сортировка, но недостаток стандартной функции в том, что она работает только с массивами типа int.

Более полезной практически будет функция сортировки с двумя массивами. По одному массиву выполняются условия сортировки, но сортируются оба массива. Например, в первом массиве находятся цены открытия позиций, а во втором тикеты, необходимо упорядочить массивы по возрастанию цены открытия, а затем последовательно выполнить с ними какие-то действия, используя тикеты. В этом случае шаблон будет с двумя типами, пусть будут типы A и B:

template <typename A, typename B>
void BubbleSortUp2(A & a[],B & b[]){
   for(int i=ArraySize(a)-1;i>0;i--){
      for(int j=0;j<i;j++){
         if(a[j]>a[j+1]){
            A tmp=a[j];
            a[j]=a[j+1];
            a[j+1]=tmp;
            B tmp2=b[j];
            b[j]=b[j+1];
            b[j+1]=tmp2;
         }
      }
   }
}

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

Подобным образом можно записать шаблон класса или структуры.

Допустим у нас есть класс упрощающий работу с массивами:

class CMyArray{
   protected:
      int m_a[];
      int m_cnt;
   public:
      void CMyArray(){
         m_cnt=0;
      }
      void Set(int i,int val){
      if(i>=ArraySize(m_a))ArrayResize(m_a,i+1024);
         m_a[i]=val;
         m_cnt=MathMax(m_cnt,i+1);
      }
      int Get(int i){
         if(i<0 || i>=m_cnt)return 0;
         return m_a[i];
      }
      int Cnt(){
         return m_cnt;
      }
};
При использовании этого класса нет необходимости беспокоиться об увеличении длины массива, а так же беспокоиться, что бы при извлечении значения из массива, не выйти за пределы его границ. При установке значения какого-либо элемента массива, в случае необходимости, размер массива будет увеличен. А при извлечении значения, если индекс элемента выходит за пределы массива, будет получено значение 0. Имейте ввиду, если выполнять присвоение значений элементам массива не последовательно, то промежуточные элементы массива могут иметь неожиданные значения (могут содержать мусор). Необходимо знать, что массив нужно заполнять последовательно, что собственно не противоречит разумной работе с массивами.

В данном случае шаблон создается точно так же, как и с функцией:
template <typename T>
class CMyArray{
   protected:
      T m_a[];
      int m_cnt;
   public:
      void CMyArray(){
         m_cnt=0;
      }
      void Set(int i,T val){
         if(i>=ArraySize(m_a))ArrayResize(m_a,i+1024);
         m_a[i]=val;
         m_cnt=MathMax(m_cnt,i+1);
      }
      T Get(int i){
         if(i<0 || i>=m_cnt)return 0;
         return m_a[i];
      }
      int Cnt(){
         return m_cnt;
      }
};

А вот создания объекта данного класса имеет особенность – необходимо указывать тип массива, нужно писать не как обычно:

CMyArray a;

В треугольных скобках надо указать тип:

CMyArray<int> a;

Теперь сохраним в массиве несколько значений и проверим их:

a.Set(a.Cnt(),1);
a.Set(a.Cnt(),2);
a.Set(a.Cnt(),3);
for(int i=0;i<a.Cnt();i++){
   Print(a.Get(i));
}

Шаблон класса, так же как и шаблон функции может содержать несколько универсальных типа. После этой статьи, если потребуется, вы сможете самостоятельно справиться с этой задачей. Сейчас же сделаем более полезную доработку данного класса. Сейчас ,при необходимости увеличить размер массива, он увеличивается блоками по 1024 элемента. Добавим возможность менять размер блока при создании объекта. В конструктор класса передадим необязательный параметр со значением по умолчанию 1024. Для размера блока объявлена переменная m_bs, а все основные действия выполняются в конструкторе класса:

template <typename T>
class CMyArray{
   protected:
      T m_a[];
      int m_cnt;
      int m_bs;
   public:
      void CMyArray(int bs=1024){
         m_cnt=0;
         m_bs=bs;
      }
      void Set(int i,T val){
         if(i>=ArraySize(m_a))ArrayResize(m_a,i+m_bs);
         m_a[i]=val;
         m_cnt=MathMax(m_cnt,i+1);
      }
      T Get(int i){
         if(i<0 || i>=m_cnt)return 0;
         return m_a[i];
      }
      int Cnt(){
         return m_cnt;
      }
};

Теперь объект можно создать как и раньше:

CMyArray<int> a;

А можно с указанием размера блока, например:

CMyArray<int> a(64);

В этом случаем размер блока 64 элемента.