Шаблоны функций в С++

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

На самом деле, шаблоны функций -это мощный инструмент в С++, который намного упрощает труд программиста. Например, нам нужно запрограммировать функцию, которая выводила бы на экран элементы массива. Задача не сложная! Но, чтобы написать такую функцию, мы должны знать тип данных массива, который будем выводить на экран. И тут нам говорят — тип данных не один, мы хотим, чтобы функция выводила массивы типа int, double, float и char.

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

// перегрузка функции printArray для вывода массива на экран
void printArray(const int * array, int count)
{
    for (int ix = 0; ix < count; ix++)
        cout << array[ix] << "   ";
    cout << endl;
}

void printArray(const double * array, int count)
{
    for (int ix = 0; ix < count; ix++)
        cout << array[ix] << "   ";
    cout << endl;
}

void printArray(const float * array, int count)
{
    for (int ix = 0; ix < count; ix++)
        cout << array[ix] << "   ";
    cout << endl;
}

void printArray(const char * array, int count)
{
    for (int ix = 0; ix < count; ix++)
        cout << array[ix] << "   ";
    cout << endl;
}

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

И, если запустить программу с этими функциями, то она будет исправно работать. Компилятор сам будет определять какую функцию использовать при вызове.

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

Мы создаем один шаблон, в котором описываем все типы данных. Таким образом исходник не будет захламляться никому ненужными строками кода. Ниже рассмотрим пример программы с шаблоном функции. Итак, вспомним условие: «запрограммировать функцию, которая выводила бы на экран элементы массива».

#include "stdafx.h"
#include <iostream>
#include <cstring>
using namespace std;
// шаблон функции printArray
template <typename T>
void printArray(const T * array, int count)
{
    for (int ix = 0; ix < count; ix++)
        cout << array[ix] << "   ";
    cout << endl;
} // конец шаблона функции printArray
int main()
{
    // размеры массивов
    const int iSize = 10,
              dSize = 7,
              fSize = 10,
              cSize = 5;
    // массивы разных типов данных
    int    iArray[iSize] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    double dArray[dSize] = {1.2345, 2.234, 3.57, 4.67876, 5.346, 6.1545, 7.7682};
    float  fArray[fSize] = {1.34, 2.37, 3.23, 4.8, 5.879, 6.345, 73.434, 8.82, 9.33, 10.4};
    char   cArray[cSize] = {"MARS"};
    cout << "\t\t Шаблон функции вывода массива на экран\n\n";
    // вызов локальной версии функции printArray для типа int через шаблон
    cout << "\nМассив типа int:\n"; printArray(iArray, iSize);
    // вызов локальной версии функции printArray для типа double через шаблон
    cout << "\nМассив типа double:\n"; printArray(dArray, dSize);
    // вызов локальной версии функции printArray для типа float через шаблон
    cout << "\nМассив типа float:\n"; printArray(fArray, fSize);
    // вызов локальной версии функции printArray для типа char через шаблон
    cout << "\nМассив типа char:\n";printArray(cArray, cSize);
    return 0;
}

// код Code::Blocks

// код Dev-C++

#include <iostream>
#include <cstring>
using namespace std;
// шаблон функции printArray
template <typename T>
void printArray(const T * array, int count)
{
    for (int ix = 0; ix < count; ix++)
        cout << array[ix] << "   ";
    cout << endl;
} // конец шаблона функции printArray
int main()
{
    // размеры массивов
    const int iSize = 10,
              dSize = 7,
              fSize = 10,
              cSize = 5;
    // массивы разных типов данных
    int    iArray[iSize] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    double dArray[dSize] = {1.2345, 2.234, 3.57, 4.67876, 5.346, 6.1545, 7.7682};
    float  fArray[fSize] = {1.34, 2.37, 3.23, 4.8, 5.879, 6.345, 73.434, 8.82, 9.33, 10.4};
    char   cArray[cSize] = {"MARS"};
    cout << "\t\t Шаблон функции вывода массива на экран\n\n";
    // вызов локальной версии функции printArray для типа int через шаблон
    cout << "\nМассив типа int:\n"; printArray(iArray, iSize);
    // вызов локальной версии функции printArray для типа double через шаблон
    cout << "\nМассив типа double:\n"; printArray(dArray, dSize);
    // вызов локальной версии функции printArray для типа float через шаблон
    cout << "\nМассив типа float:\n"; printArray(fArray, fSize);
    // вызов локальной версии функции printArray для типа char через шаблон
    cout << "\nМассив типа char:\n";printArray(cArray, cSize);
    return 0;
}

Заметьте, код уменьшился в 4 раза, так как в программе объявлен всего один экземпляр функции — шаблон. В main я объявил несколько массивов — четыре, для типов данных: int, double, float, char. После чего, в строках 26, 28, 30, 32, выполняется вызов функции printArray для разных массивов. Результат работы программы показан ниже.

CppStudio.com
		 Шаблон функции вывода массива на экран

Массив типа int:
1   2   3   4   5   6   7   8   9   10   

Массив типа double:
1.2345   2.234   3.57   4.67876   5.346   6.1545   7.7682   

Массив типа float:
1.34   2.37   3.23   4.8   5.879   6.345   73.434   8.82   9.33   10.4   

Массив типа char:
M   A   R   S

Как видите программа корректно работает, и для этого нам понадобилось всего один раз определить функцию printArray в привычном для нас виде. Обратите внимание, что перед объявлением самой функции, в строке 5, стоит следующая запись template<typenameT>. Как раз эта запись и говорит о том, что функция printArray на самом деле является шаблоном функции, так как в первом параметре printArray стоит тип данных const T*, точно такой же как и в строке 5.

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

template <class T>

или

template <typename T>

или

template <typename T1, typename T2>

Ключевое слово typename говорит о том, что в шаблоне будет использоваться встроенный тип данных, такой как: int, double, float, char и т. д. А ключевое слово class сообщает компилятору, что в шаблоне функции в качестве параметра будут использоваться пользовательские типы данных, то есть классы.

У нас в шаблоне функции использовались встроенные типы данных, поэтому в строке 5 мы написали template<typenameT>. Вместо T можно подставить любое другое имя, какое только придумаете. Давайте подробно рассмотри фрагмент кода из верхней программы, я его вынесу отдельно.

// шаблон функции printArray
template <typename T>
void printArray(const T * array, int count)
{
    for (int ix = 0; ix < count; ix++)
        cout << array[ix] << "   ";
    cout << endl;
} // конец шаблона функции printArray

В строке 2 выполняется определение шаблона с одним параметром — T, причем этот параметр будет иметь один из встроенных типов данных, так как указано ключевое слово typename.

Ниже, в строках 3 — 8 объявлена функция, которая соответствует всем критериям объявления обычной функции, есть заголовок, есть тело функции, в заголовке есть имя и параметры функции, все как обычно. Но что эту функции превращает в шаблон функции, так это параметр с типом данных T, это единственная связь с шаблоном, объявленным ранее. Если бы мы написали

void printArray(const int * array, int count)
{
    for (int ix = 0; ix < count; ix++)
        cout << array[ix] << "   ";
    cout << endl;
}

то это была бы простая функция для массива типа int.

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

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

#include "stdafx.h"
#include <iostream>
#include <cstring>
using namespace std;
// шаблон функции для поиска максимального значения в массиве
template <typename T>
T searchMax(const T* array, int size)
{
    T max = array[0]; // максимальное значение в массиве
    for (int ix = 0; ix < size; ix++)
        if (max < array[ix])
            max = array[ix];
    return max;
}
int main()
{
    // тестируем шаблон функции searchMax для массива типа char
    char array [] = "aodsiafgerkeio";
    int len = strlen(array);
    cout << "Максимальный элемент массива типа char: " << searchMax(array, len) << endl;
    // тестируем шаблон функции searchMax для массива типа int
    int iArray [5] = {3,5,7,2,9};
    cout << "Максимальный элемент массива типа int: " << searchMax(iArray, 5) << endl;
    return 0;
}

// код Code::Blocks

// код Dev-C++

#include <iostream>
#include <cstring>
using namespace std;
// шаблон функции для поиска максимального значения в массиве
template <typename T>
T searchMax(const T* array, int size)
{
    T max = array[0]; // максимальное значение в массиве
    for (int ix = 0; ix < size; ix++)
        if (max < array[ix])
            max = array[ix];
    return max;
}
int main()
{
    // тестируем шаблон функции searchMax для массива типа char
    char array [] = "aodsiafgerkeio";
    int len = strlen(array);
    cout << "Максимальный элемент массива типа char: " << searchMax(array, len) << endl;
    // тестируем шаблон функции searchMax для массива типа int
    int iArray [5] = {3,5,7,2,9};
    cout << "Максимальный элемент массива типа int: " << searchMax(iArray, 5) << endl;
    return 0;
}

Вот вам еще один пример использования шаблонов функций. Шаблон функции объявлен в строках 5-13. Функция должна возвращать максимальное значение массива, поэтому возвращаемое значение типа T, ведь тип данных массива заранее не известен. Кстати внутри функции объявлена переменная max типа T, в ней будет храниться максимальное значение массива. Как видите, тип данных T используется не только для спецификации параметров функции, но и для указания типа возвращаемого значения, а также может свободно использоваться для объявления любых переменных внутри шаблона функции.

CppStudio.com
Максимальный элемент массива типа char: s
Максимальный элемент массива типа int: 9

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

Автор: Marienko L.
Дата: 24.11.2012
Поделиться:

Комментарии

  1. TotCambIKoka

    TotCambIKoka

    А можно показать как осуществляется вывод двумерного массива?

    Пробовал сделать по аналогии, но компилятор в вижуал С++ 2008 выдает  error C2109: для индекса требуется массив или указатель

     

     

    • Артём Ролдугин

      Артём Ролдугин

      #include <cstdlib>
      #include <iomanip>
      #include <iostream>
      using std::cout;
      using std::setw;
      template <typename T>
      void displayMatrix(T** const, int, int);
      int main()
      {
       srand(time(NULL));
       int m=10+rand()%11;
       int n=10+rand()%11;
       int **intMatrix = new int*[m];
       for(int i=0; i<m; ++i)
       intMatrix[i] = new int[n];
       for(int i=0; i<m; ++i)
       for(int j=0; j<n; ++j)
       intMatrix[i][j]=rand()%100;
       displayMatrix(intMatrix, m, n);
       delete intMatrix;
       char **charMatrix = new char*[m];
       for(int i=0; i<m; ++i)
       charMatrix[i] = new char[n];
       for(int i=0; i<m; ++i)
       for(int j=0; j<n; ++j)
       charMatrix[i][j]=97+rand()%26;
       displayMatrix(charMatrix, m, n);
       delete charMatrix;
       return 0;
      }
      template <typename T>
      void displayMatrix(T** const array, int x, int y)
      {
       for(int i=0; i<x; ++i)
       {
        for(int j=0; j<y; ++j)
        cout<<setw(3)<<array[i][j];
        cout<<'\n';
       }
       cout<<'\n';
      }

Оставить комментарий

Вы должны войти, чтобы оставить комментарий.