Шаг 35.
Шаблоны функций

    На этом шаге мы рассмотрим определение и использование шаблонов функций.

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

    В определении шаблона семейства функций используется служебное слово template. Для параметризации используется список формальных параметров шаблона, который заключается в угловые скобки <>. Каждый формальный параметр шаблона обозначается служебным словом class, за которым следует имя параметра (идентификатор). Пример определения шаблона функций, вычисляющих абсолютные значения числовых величин разных типов:

            template <class type>
            type abs(type x) { return x > 0 ? x : -x; }

    Шаблон семейства функций состоит из двух частей - заголовка шаблона:

           template <список_параметров_шаблона>

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

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

             template <class T> 
             void swap (Т* х, Т* у) 
             { 
                   Т z = *х;
                   *х = *у; 
                   *у = z;
              }

    Здесь параметр T шаблона функций используется не только в заголовке для спецификации формальных параметров, но и в теле определения функций, где он задает тип вспомогательной переменной z.

    Шаблон семейства функций служит для автоматического формирования конкретных определений функций по тем вызовам, которые транслятор обнаруживает в тексте программы. Например, если программист употребляет обращение аbs (-10.3), то на основе приведенного выше шаблона компилятор сформирует такое определение функции:

         double abs(double x)   {   return x > 0  ? х   :   -х;   }

    Далее будет организовано выполнение именно этой функции и в точку вызова в качестве результата вернется числовое значение 10.3.

    Если в программе присутствует приведенный выше шаблон семейства функций swap() и появится последовательность операторов:

        long k = 4,   d =  8;   
        swap   (&k,  &d) ; 

то компилятор сформирует определение функции:

         void swap(long*  x,   long*  y) 
         {   
              long  z  = *x;
              *x =  *y;   
              *y = z; 
         }

    Затем будет выполнено обращение именно к этой функции и значения переменных k, d поменяются местами.

    Если в той же программе присутствуют операторы:

       double а = 2.44,   b =  66.3;   
       swap   (&a,   &b);

то сформируется и выполнится функция:

        void swap (double* x, double* у) 
        { 
             double z = *x;
             *x = *y; 
             *y = z ; 
         }

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

   

//OOР35_1.СРР - шаблон функций для поиска в массиве.
#include <iostream.h>
// Функция  определяет ссылку на элемент с максимальным значением:
template <class  type>
type& rmax(int n, type d[])
{
  int  im = 0;
  for (int i=1; i < n; i++)
     im = d[im] > d[i] ? im : i; 
  return d[im]; 
}
void main() 
{ 
  int n = 4;
  int x[] = { 10, 20, 30, 14 };
  // Аргумент - целочисленный массив:
  cout << "\nrmax(n,x) = " << rmax(n,x);
  rmax(n,x) = 0; // Обращение с целочисленным массивом.
  for (int i = 0; i < n; i++)
     cout << "\tx[" << i << "] = " << x[i];
  float arx[] = { 10.3, 20.4, 10.5 };
  // Аргумент - массив float:
  cout << "\nrmax(3,arx)=" << rmax(3,arx); 
  rmax(3,arx) = 0; // Обращение с массивом типа float. 
  for (i = 0; i < 3; i++)
     cout << "\tarx[" << i << "] = " << arx[i];
}
Текст этой программы можно взять здесь.

    Результат выполнения программы:

   rmax(n,x)  =  30       x[0]  = 10    x[l]  = 20    x[2] = 0    x[3]  = 14 
   rmax(3,arx) = 20.4     arx[0]   = 10.3     arx[l]   = 0     arx[2]  = 10.5

    В программе используются два разных обращения к функции rmax(). В одном случае параметр - целочисленный массив и возвращаемое значение - ссылка типа int. Во втором случае фактический параметр - имя массива типа float и возвращаемое значение имеет тип ссылки на float.

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

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

    На следующем шаге мы закончим рассматривать шаблоны функций.




Предыдущий шаг Содержание Следующий шаг