定义函数模板
模板的引入,使得类型可以作为参数出现。函数模板相比于普通的函数,它可以为多种不同的类型所调用,具体的类型只有在被调用时才会被确定。
template <typename T>
T const& max(T const& a, T const& b)
{
return (a > b)? a : b;
}
在上面的程序中,尖括号中使用关键字typename引入类型参数T。T只是一种标识符,可以用任何其他符合命名规则的标识符代替。可以在调用函数时,指定类型(基本类型、类等)。
也可引入多个类型参数,在尖括号中使用逗号隔开。
template <typename T, typename F, ...>
re-type func(parameters){func-body}
关键字typename也可以换成class,其作用一致,在历史上刚开始就是使用class,但为避免与类定义混淆,便引入typename。
函数模板实例化
下面对模板函数进行多次调用:
max(12, 34); //返回34
max(12.1, 3.2); //返回12.1
std::string a = "template";
std::string b = "class";
max(a, b); //返回template
在模板函数被调用时,编译器会对它进行自发的实例化,即用具体参数的类型替换掉定义模板函数时使用的类型参数T。
在没有指定用于替换的类型时,编译器会检查传入实参的类型,推断出用于实例化的类型,这被称为实参演绎。
当然也可以显式的指定实例化类型:
max<double>(12, 12.1);
检查到非指定类型的参数时,编译器会调用强制类型转换,使其能够比较。但如果不指定,对于传入的两个不同的参数类型,则会报错。
指定返回类型
这是一个小技巧:对于上面的代码,若模板函数接收到两个不同类型的参数时,我们经常无法判断具体的返回类型,也难以提前指定,可以使用三个类型参数来解决这一问题。
template <typename Rt, typename T1, typename T2>
Rt max(T1 const& a, T2 const& a)
{
return a > b ?a : b;
}
注意声明三个不同类型参数的顺序,下面对这个模板函数进行调用:
max<double>(12, 12.1);
首先,后两个不同的类型参数,可以避免使用一个类型参数时,传入不同类型的参数会报错的问题;而第一个类型参数用于在函数中指定返回值类型。
返回值类型声明在最前面,调用时就可只指定返回值类型,后两个类型参数则根据实参演绎确定。
注意返回值不可使用引用,因为可能返回的是经过类型转换产生的临时变量。
重载函数模板
重载函数模板与重载普通函数类似,下面的实例解释了编译器在何种情况下会选择调用何种函数(普通函数还是模板函数):
#include <iostream>
#include <string>
inline int const &max(int const &a, int const &b)
{
std::cout << "1st Called" << std::endl;
return a > b ? a : b;
}
inline char *const &max(char *const &a, char *const &b)
{
std::cout << "2nd Called" << std::endl;
return *a > *b ? a : b;
}
template <typename T>
inline T const &max(T const &a, T const &b)
{
std::cout << "3th Called" << std::endl;
return a > b ? a : b;
}
template <typename T>
inline T const &max(T const &a, T const &b, T const &c)
{
std::cout << "4th Called" << std::endl;
return max(max(a, b), c);
}
int main()
{
std::cout << ::max(12, 2) << std::endl; // 1st Called 12
std::cout << ::max(1, 2, 3) << std::endl; // 4th Called 1st Called 1st Called 3
std::cout << ::max<>(1, 2) << std::endl; // 3th Called 2
std::string a = "template", b = "class";
std::cout << ::max(a, b) << std::endl; // 3th Called template
char c[]{"template"}, d[]{"class"};
std::cout << ::max(c, d) << std::endl; // 2nd Called template
}
对于模板函数通过实例化可以实现,但普通函数已定义,编译器会优先调用普通类型的函数,但可以通过列出一个空的类型参数列表表示强制调用模板函数(对比主函数中的第一和第三个输出)。
需要注意的是,实例中第四个重载(三个参数的比较)必须放置在其他重载声明的后面,因为其内部调用了其他的重载函数。至少有一个重载的声明需要放置在这个函数前面,才能通过编译。