函数模板
函数模板是那些被参数化的函数,它们代表的是一个函数家族。
初探函数模板
函数模板提供了一种函数行为,该函数行为可以用多种不同的类型进行调用;也就是说,函数模板代表一个函数家族。它的表示(即外形)看起来和普通的函数很相似,唯一的区别是函数元素是未确定的:这些元素将在使用时被参数化。
定义模板
下面是一个返回两个值中最大者的函数模板:
template <typename T> inline T const& max(T const &a,T const &b) { return a<b?b:a; }
使用模板
下面的程序展示了如何使用max()函数模板:
#include <iostream> #include<string> #include"max.hpp" int main() { i=42; std::cout<<"max(7,i)="<<::max(7,i)<<std::endl; double f1=3.4; double f2=-6.7; std::cout<<"max(f1,f2)="<<::max(f1,f2)<<std::endl; std::string s1="mathcmatics"; std::string s2="math"; std::cout<<"max(s1,s2)="<<max(s1,s2)<<std::endl; }
在这个程序中,max()被调用了3次,调用实参每次都不相同:一次用两个int,一个用两个double,一次用两个string。
可以看到:max()模板每次调用的前面都有域限定符::,这是为了确认我们调用的是全局名字空间中的max()。因为标准库也有一个std::max()模板,在某些情况下也可以被使用,因此有时还会产生二义性。
通常而言,并不是把模板编译成一个可以处理任何类型的单一实体;而是对于实例化模板参数的每种类型,都从模板产生出一个不同的实体。因此,针对3种类型中的每一种,max()都被编译了一次。例如,max()的第一次调用:
int i=42;
max(7,i);
使用了以int为模板参数T的函数模板。因此,它具有调用如下代码的语义:
inline int const& max(int const& a,int const& b) { return a<b?b:a; }
这种用具体类型代替模板参数的过程叫做实例化。它产生了一个模板的实例。
可以看到:只要使用函数模板,(编译器)会自动地引发这样一个实例化过程,因此程序员并不需要额外地请求模板的实例化。
如果试图机遇与一个不支持模板内部所使用操作的类型实例化一个模板,那么将会导致一个编译器错误,例如:
std::complex<float> c1,c2;//std::complex并不支持operator<
max(c1,c2); //编译器错误
于是,我们可以得出一个结论:模板被编译了两次,分别发生在
1 实例化之前,先检查模板代码本身,查看语法是否正确;在这里会发现错误的语法,如遗漏分号等。
2 在实例化期间,检查模板代码,查看是否所有的调用都有效。在这里会发现无效的调用,如该实例化类型不支持某些函数调用等。
注意:使用函数模板,并且引发模板实例化的时候,编译器(在某时刻)需要查看模板的定义。这就不同于普通函数中编译和连接之间的区别。因为对普通函数而言,只要有该函数的声明(即不需要定义),就可以顺利通过编译。
实参的演绎
当我们为某些实参调用一个诸如max()的模板时,模板参数可以由我们所传递的实参来决定。如果我们传递了两个int给参数类型T const&,那么C++编译器能够得出结论:T必须是int。注意,这里不允许进行自动类型转换,每个T都必须正确地匹配。例如,
template <typename T>
inline T const& max(T const& a,T const& b)
max(4,7); //OK:两个实参的类型是int
max(4,4.2); //ERROR:第一个T是int,而第二个是double
有3种方法可以用来处理上面这个错误:
1 对实参进行强制类型转换,使他们可以互相匹配:
max(static_cast<double>(4),4.2); //OK
2显示指定(或者限定)T的类型:
max<double>(4,4.2);
3 指定两个参数可以具有不同的类型。