yandex rtb 1
ГоловнаЗворотній зв'язок
yande share

Объектно-ориентированное программирование

Лекция 10.

Шаблоны функций. Шаблонные классы.

 

Часто приходится иметь дело с случаем, когда несколько функций должны выполнять схожие действия с данными разных типов. В таких случаях мог бы быть использован механизм перегруженных функций, т.е. мы могли бы написать несколько вариантов одной и той же функции с различными значениями аргументов. Это применимо, если алгоритмы обработки для разных типов данных несколько отличаются. А как быть, если независимо от типа аргументов функции должны выполнять одни и те же действия? Например, копирование одного массива в другой, обмен значениями переменных и т.д. выполняются одинаково для разных типов данных. В таких случаях в языке С++ предусмотрен механизм шаблонов. Определение шаблонных функций делает возможным повторное использование кода безопасным с точки зрения типов образом, что позволяет компилятору автоматизировать процесс инстанцирования типа (фактический тип заменяет тип-параметр, присутствующий в коде шаблона). Механизм шаблонов обеспечивает параметрический полиморфизм.

Шаблон функции определяется следующим образом:

template<шаблонные_аргументы> объявление

Шаблонными аргументами служат

сlass идентификатор

и/или

объявление_аргумента

Аргументы class идентификатор инстанцируются типом. Другие аргументы объявления инстанцируются постоянными выражениями не с плавающей точкой и могут быть функцией или адресом объекта с внешней компоновкой. Например:

template<class T,int n>

(Заметим, что n должна быть использована в объявлении).

Шаблон может быть написан с использованием параметров по умолчанию (только для классов):

template<class T=double, int n=100>

Аргументом шаблона может быть другой шаблон.

Рассмотрим пример определения шаблонной функции:

#include <iostream.h>

#include <string.h>

#define MAX_LEN 255

template<class T>

void shiftleft(T a[],int start, int end) {

   cout<<"in template shiftleft"<<endl;

   T temp;

   temp=a[start];

   for (int i=start; i<end; i++)

               a[i]=a[i+1];

   a[end]=temp;

}

void shiftleft(char a[][MAX_LEN],int start, int end) {

   cout<<"in string shiftleft"<<endl;

   char temp[MAX_LEN];

   strcpy(temp,a[start]);

   for (int i=start; i<end; i++)

               strcpy(a[i],a[i+1]);

   strcpy(a[end],temp);

}

void main() {

   int i;

   char str[]="abcdef";

     char words[][MAX_LEN]= {"word00","word01","word02","word03","word04","word05"};

   int dim[]={0,1,2,3,4,5};

   shiftleft(str,0,2);

   cout<<str<<endl;

   shiftleft(dim,0,2);

   for (i=0;i<6;i++)

               cout<<dim[i]<<' ';

   cout<<endl;

   shiftleft(words,0,2);

   for (i=0;i<6;i++)

               cout<<words[i]<<' ';

   cout<<endl;

}

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

Результат работы программы:

in template shiftleft

bcadef

in template shiftleft

1 2 0 3 4 5

in string shiftleft

word01 word02 word00 word03 word04 word05

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

Выбор перегруженной функции производится по следующему алгоритму:

1.          Строгое соответствие (при допущении некоторых тривиальных преобразований) для нешаблонных функций.

2.          Строгое соответствие для шаблонов функций

3.          Обычное разрешение аргументов для нешаблонных функций.

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

 Заметим, что  функция может иметь несколько шаблонов. Например, можно написать следующую параметризованную функцию для разобранной выше программы (предполагающую, что начальный индекс равен 0):

template<class T>

void shiftleft(T a[], int end) {

   cout<<"in template shiftleft without start"<<endl;

   T temp;

   temp=a[0];

   for (int i=0; i<end; i++)

               a[i]=a[i+1];

   a[end]=temp;

}

Механизм шаблонов может быть применен и при определении классов. Например, при определении таких классов как стек, список, дерево и т.п. нам безразлично, какую природу имеют элементы. Было бы нелогично описывать стек отдельно для целых чисел, отдельно – для действительных, отдельно – для символов. Проще использовать, как и в случае функций, шаблоны. Как и для функций, объявление класса предваряет объявление шаблона.

Рассмотрим пример простого параметризованного класса pair. Заметим, что в классе используются сразу два параметра типа.

#include <iostream.h>

template<class T1, class T2>

class pair {

protected:

   T1 first;

   T2 second;

public:

   pair(T1 f,T2 s):first(f),second(s) { }

   void setfirst(T1 f) { first=f;}

   T1 getfirst() {return first;}

   void setsecond(T2 s) {second=s;}

   T2 getsecond();

};

template<class T1, class T2>

T2 pair<T1,T2>::getsecond() {return second;}

class charpair:public pair<char,char> {

public:

   charpair(char c1, char c2):pair<char,char>(c1,c2) { }

};

void main() {

   pair<char,int> a('a',1);

   cout<<"first="<<a.getfirst()<<" second="<<a.getsecond()<<endl;

   charpair b('b','c');

   cout<<"first="<<b.getfirst()<<" second="<<b.getsecond()<<endl;

}

Из примера видно, что параметризованный класс может быть базовым классом при создании производного класса (charpair в примере). Производный класс также может быть параметризованным. Например, мы можем изменить производный класс charpair, обобщив его так:

template<class T>

class eqpair:public pair<T,T> {

public:

   eqpair(T c1, T c2):pair<T,T>(c1,c2) { }

};

При этом конкретное объявление объекта класса будет производиться следующим образом:

   eqpair<char> b('b','c');

 

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

Статические переменные не универсальны, они специфичны для каждого инстанцирования. Например, если бы класс eqpair содержал бы объявление

 private:

   static int count;

то переменные

eqpair<char>::count

eqpair<int>::count

различались бы между собой.

 

12