Немного ранее была рассмотрена тема о перегрузке функций в C++. Вы узнали о возможности написать несколько функций с одинаковым именем, которые выполняют одинаковые задачи. Основное отличие между ними – сигнатура (типы параметров и/или количество параметров). При запуске программы, компилятор сам выберет, какую из перегруженных функций применить (исходя из того, что передано в функцию)
Шаблоны так же позволяют функциям обрабатывать разные типы данных, переданные в виде параметров. Но при этом достаточно написать одно общее определение функции. Смотрите как это выглядит.
Определяя шаблон функции мы использовали зарезервированные слова C++: template и typename. template говорит о том, что сейчас будет определен шаблон. А в угловых скобках после typename дается условное имя типу данных. Тут, вместо имени T, можно присвоить любое (очень желательно корректное) имя.
Далее определяется сама функция. Тип возвращаемого значения указываем T. Параметры: число типа T, и число типа int. Эта функция считает процент от числа и возвращает значение в программу. И число, и процент передаются как параметры. Например первым параметром мы передадим в функцию целое число (100). Во время запуска, компилятор отметит себе что T это int и заменит в шаблоне функции все эти T на int. Таким образом для компилятора функция примет вид:
Мы этого не увидим явно, но все отработает замечательно. Какое бы число мы не передали в эту функцию первым параметром, компилятор создаст экземпляр шаблона функции для любого вызова. При этом первый параметр может иметь любой тип данных.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <iostream> using namespace std; template <typename T> T calcPercent(T number, int percent) { return number * percent / 100; } int main() { setlocale(LC_ALL, "rus"); cout << "20% от 200: " << calcPercent(200, 20) << endl; cout << "10% от 42.56: " << calcPercent(42.56, 10) << endl; return 0; } |
В итоге на экране увидим такой результат расчетов:
Говоря об определении шаблона функции, хочется добавить, что в угловые скобки после ключевого слова template возможно записать несколько условных имен для типов данных. Зачем это может понадобиться? Рассмотрим такой пример:
Необходимо написать функцию, которая примет два числа, определит максимальное из них и вернет его в программу. Будем иметь ввиду, что в функцию мы можем передать числа разных типов. Возможен и случай, что одно число будет целым, а второе – вещественным. Смотрите что произойдет:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include <iostream> using namespace std; template <typename T> T findMax(T firstNum, T secondNum) { if (firstNum > secondNum) return firstNum; else if (secondNum > firstNum) return secondNum; else return 0; } int main() { setlocale(LC_ALL, "rus"); cout << "Сравниваем 255 и 700! Большее = " << findMax(255, 700) << endl; cout << "Сравниваем 8.5 и 4.9! Большее = " << findMax(8.5, 4.9) << endl; // при передаче 2-х параметров с разными типами возникнет ошибка cout << "Сравниваем 100 и 5.1! Большее = " << findMax(100, 5.1) << endl; return 0; } |
Первый и второй параметры функции определены, как параметры типа T. С вызовами функции в строках 19-20 проблем не возникает, так как передаваемые параметры имеют одинаковый тип данных. Проблемы возникнут в строке 22 при компиляции. И это понятно. Компилятор запутается. Он не может преобразовать тип int в double.
Чтобы обойти эту проблему, надо написать другой шаблон.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include <iostream> using namespace std; template <typename T_1, typename T_2> T_2 findMax(T_1 firstNum, T_2 secondNum) { if (firstNum > secondNum) return firstNum; else if (secondNum > firstNum) return secondNum; else return 0; } int main() { setlocale(LC_ALL, "rus"); cout << "Сравниваем 255 и 700! Большее = " << findMax(255, 700) << endl; cout << "Сравниваем 8.5 и 4.9! Большее = " << findMax(8.5, 4.9) << endl; // ошибки не будет: cout << "Сравниваем 100 и 5.1! Большее = " << findMax(100, 5.1) << endl; return 0; } |
Тут typename T_1 обозначает тип параметра, который передается в функцию первым. typename T_2 соответственно обозначает тип второго параметра. Такой вариант определения шаблона функции предупредит ошибки, возникающие при передаче параметров разных типов.
Важно запомнить,что если вы в угловых скобках вы даете несколько условных имен для типов параметров функции (как в нашем примере), все эти имена типов должны быть упомянуты в сигнатуре функции. Иначе не избежать ошибки при компиляции. Например такое определение шаблона
приводит к следующим ошибкам:
В шаблоне мы определили условные им типов T_1 и T_2, а в сигнатуре прописали только тип T_1 .
На практике вы можете встретить определение шаблона функции таким способом, где вместо ключевого слова typename используется слово class. Например:
оно равнозначно тому определению, которое мы рассматривали
Ранее, до выхода стандарта C++98, в шаблонах всегда использовали слово class. Сейчас же лучше, когда появилось ключевое слово typename , лучше применять его. Так как оно более явно говорит о том, что имена T_1 и T_2 представляют тип.
Подведем итог. Шаблон функции предназначен для создания обобщенного описания функции. Такая функция сможет принять параметры любого типа. Шаблон позволит компилятору генерировать код функции для конкретного типа (или типов) данных, который был передан в неё при вызове.
Обязательно посмотрите видео о шаблонах:
Странно, у меня если даже определить шаблон через T_1 программа работает корректно =о
Такой вопрос, вот в примере, где функция принимает два аргумента с параметрами T1 и T2, у вас написано, что функция вернет большее значение с типом T2, а если большее число окажется то, которое с параметром T1 ? будет неявное приведение к типу T2 с возможной потерей данных ??
Да, в показанном примере возвращаемое значение функции типа T1, и если возвращается second типа T2, то будет неявное преобразование к T1.
А что будет в итоге – это вы должны сами предусмотреть … например, можете записать явное преобразование:
return static_cast( second );
почему так нельзя?
template
T_1 findMax(int a, double b)
{
if (a > b)
return a;
else if (b > a)
return b;
else
return 0;
}
template
T_1 findMax(int a, double b)
{
if (a > b)
return a;
else if (b > a)
return b;
else
return 0;
}
то есть так почему нельзя?
template
T_1 findMax(int a, double b)
{
if (a > b)
return a;
else if (b > a)
return b;
else
return 0;
}
Потому, что есть правила синтаксиса, и это просто не пройдёт синтаксический контроль.
А синтаксические правила шаблонов говорят, что типы шаблона записываются в угловых скобках.
Сайт скушал часть кода, почему так нельзя?
#include
using namespace std;
template
T findMax(int a, double b)
{
if (a > b)
return a;
else if (b > a)
return b;
else
return 0;
}
int main()
{
cout << findMax(1, 5.42);
return 0;
}
там где template еще сайт скушал
тайпнэйм Т в угловых скобках
Так нельзя по многим причинам:
– упоминаемый шаблонный тип должен быть использован в сигнатуре функции (такая фраза есть в тексте)
– потому что возвращать (return) разные типы вам позволено только потому, что включается допустимое автоматическе преобразование – это плохая практика…
– лучше при возврате записать явное преобразование:
return (T)b;
– а ещё лучше:
return static_cast( b);
P.S. Да, движок форума (пока?) съедает всё, что в угловых скобках … поэтому я не могу вам правильно показать static_cast… , ну, в книжках посмотрите.
Обратите внимание (это не акцентировано в тексте), что всё, что связано с шаблонами (функций, классов) обрабатывается **препроцессором**, средствами макрообработки текста – до начала самой компиляции С++ все шаблоны раскрываются преобразованием кода.
Спасибо