Шаблоны функций, своими словами,— это инструкции, согласно которым создаются локальные версии шаблонированной функции для определенного набора параметров и типов данных.
На самом деле, шаблоны функций -это мощный инструмент в С++, который намного упрощает труд программиста. Например, нам нужно запрограммировать функцию, которая выводила бы на экран элементы массива. Задача не сложная! Но, чтобы написать такую функцию, мы должны знать тип данных массива, который будем выводить на экран. И тут нам говорят — тип данных не один, мы хотим, чтобы функция выводила массивы типа 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
для разных массивов. Результат работы программы показан ниже.
Шаблон функции вывода массива на экран Массив типа 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
используется не только для спецификации параметров функции, но и для указания типа возвращаемого значения, а также может свободно использоваться для объявления любых переменных внутри шаблона функции.
Максимальный элемент массива типа char: s Максимальный элемент массива типа int: 9
Шаблоны функций также можно перегружать другими шаблонами функций, изменив количество передаваемых параметров в функцию. Еще одной особенностью перегрузки является то, что шаблонные функции могут быть перегружены обычными не шаблонными функциями. То есть указывается то же самое имя функции, с теми же параметрами, но для определенного типа данных, и все будет корректно работать.
Комментарии
Excluser84
Шаблонные параметры: typename и class. Есть ли разница?
TotCambIKoka
А можно показать как осуществляется вывод двумерного массива?
Пробовал сделать по аналогии, но компилятор в вижуал С++ 2008 выдает error C2109: для индекса требуется массив или указатель
Артём Ролдугин