函数模板是指这样的一类函数:能够用多种不一样数据类型的参数进行调用,表明了一个函数家族。它的外表和普通的函数很类似,惟一的区别就是:函数中的有些元素是未肯定的,这些元素将在使用的时候才被实例化。先来看一个简单的例子:ios
下面的这个例子就定义了一个模板函数,它会返回两个参数中最大的那一个:函数
// 文件:"max.hpp" template<typename T> inline const T& max(const T& x, const T& y) { return x < y ? y : x; }
这个函数模板定义了一个“返回两个值中最大者”的函数家族,而参数的类型尚未肯定,用类型模板参数T
来肯定。模板参数须要使用以下的方式来声明:spa
template< 模板参数列表 >
在这个例子中,模板参数列表为:typename T
。关键字typename
引入了T
这个类型模板参数。固然了,可使用任何标识符做为类型模板参数的名称。咱们可使用任何类型(基本数据类型、类类型)来实例化该函数模板,只要所使用的数据类型提供了函数模板中所须要的操做便可。例如,在这个例子中,类型T
须要支持operator <
,由于a和b就是经过这个操做符来比较大小的。设计
鉴于历史缘由,也可使用关键字class
来取代typename
来定义类型模板参数,然而应该尽量地使用typename
。指针
下面的程序使用了上面定义的这个函数模板:code
#include <iostream> #include <string> #include "max.hpp" using namespace std; int main(int argc, char *argv[]) { cout << max(4, 3) << endl; // 使用int类型实例化了函数模板,并调用了该函数实例。 cout << max(4.0, 3.0) << endl; // 使用double类型实例化了函数模板,并调用了该函数实例。 cout << max(string("hello"), string("world")) << endl; // 使用string类型实例化了函数模板, // 并调用了该函数实例。 return 0; }
一般而言,并非把模板编译成一个能够处理任何类型的单一实体,而是针对于实例化函数模板参数的每种类型,都从函数模板中产生出一个独立的函数实体。所以,针对于每种类型,模板代码都被编译了一次。这种用具体类型代替模板参数的过程,叫作模板的实例化。它产生了一个新的函数实例(与面向对象程序设计中的实例化不一样)。对象
若是试图基于一个不支持模板内部所使用的操做的类型实例化一个模板,那么将会引起一个编译期错误:编译器
std::complex<double> c1, c2; max(c1, c2); // 编译错误:std::complex并不支持运算符<
因此说:模板被编译了两次,分别发生于:string
因此这引起了一个重要的问题:当使用函数模板而且引起模板实例化时,编译器必须查看模板的定义。事实上,这就不一样于普通的函数,由于对于普通的函数而言,只要有函数的声明(甚至不须要定义),就能够顺利地经过编译期。io
当咱们为某些实参调用一个函数模板时,模板参数能够由咱们所传递的实参来决定。
注意:函数模板在推断参数类型时,不容许自动类型转换,每一个类型模板参数都必须正确的匹配。
template<typename T> inline const T& max(const T& x, const T& y) { return x < y ? y : x; } int main() { // 不能这样调用: // max(10, 20.0); // 错误,由于函数模板中的类型推断拒绝隐式类型转换 // 这是由于,没法肯定到底应该使用哪一个参数类型来实例化这个模板函数。 // 因此,C++拒绝了这种作法。 可用的解决方案: ::max(static_cast<double>(10), 20.0); // OK,由于两个参数都为double。 ::max<double>(10, 20.0); // OK, 显示指定参数,这样能够尝试对参数进行类型转换。 return 0; }
注意:模板实参推断并不适合返回类型。由于返回类型并不会出如今函数调用参数的类型里面。
因此,必需要显示地指定返回类型:
template<typename T1, typename T2, typename RT> inline RT func() { // ... return RT(); } int main(int argc, char *argv[]) { func<int>(); // 必须这样显示地指定返回类型才能够,没法进行自动类型推断。 return 0; }
和普通的函数同样,函数模板也能够被重载。在下面的例子中,一个非模板函数能够和一个同名的函数模板同时存在,这称为函数模板的特化。并且该函数模板还被实例化为这个非模板函数。
// #1 inline const int& max(const int& a, const int& b) { return a < b ? b : a; } // #2 template<typename T> inline const T& max(const T& a, const T& b) { return a < b ? b : a; } // #3 template<typename T> inline const T& max(const T& a, const T& b, const T& c) { return max(max(a, b), c); } int main(int argc, char *argv[]) { /*01*/max(7, 42, 68); // 调用#3 /*02*/max(7.0, 6.0); // 调用#2 /*03*/max('a', 'b'); // 调用#2 /*04*/max(7, 42); // 调用#1 /*05*/max<>(7, 42); // 调用#2 /*06*/max<double>(7, 42); // 调用#2可是没有推断参数 /*07*/max('a', 42.7); // 调用#1 return 0; }
总结以下:
在重载函数模板时,请谨记:将对函数声明的改变限制在如下两种状况中:
不然,极可能会致使非预期的结果,例如在下面的例子中,模板函数是使用引用进行传参的,然而在其中的一个重载中(其实是针对char*进行的特化),却使用了值传递的方式,这将会致使不可预期的结果:
template<typename T> inline const T& max(const T& a, const T& b) { return a < b ? b : a; } // #2: 存在隐患,由于其它的重载都是以引用传递参数,而这个重载版本 // 却使用了值传递,不符合上面介绍的须要遵照的两个可变条件。 inline const char* max(const char* a, const char* b) { return std::strcmp(a, b) < 0 ? b : a; } template<typename T> inline const T& max(const T& a, const T& b, const T& c) { // 这里对max(a, b)的调用,若是调用了函数#2, // 那么将会返回一个局部的值,若是刚好这个局部的值 // 又比c大,那么将会返回一个指向局部变量的指针, // 这是很危险的(非预期的行为)。 return max( max(a, b), c ); } int main() { char str1[] = "frederic"; char str2[] = "anica"; char str3[] = "lucas"; char* p1 = str1; char* p2 = str2; char* p3 = str3; // 这种用法是错的,这是由于: // max(a, b)返回的是一个指针,这个指针是一个局部的对象, // 而且这个局部的对象颇有可能会被返回。 auto result = max(p1, p2, p3); return 0; }