面向对象编程(OOP)和泛型编程都能处理在编写程序时不知道类型的情况。不同之处在于:OOP能处理类型在程序运行之前都未知的情况,而在泛型编程中,在编译时就能获知类型了。模板是C++中泛型编程的基础,一个模板就是一个创建类或者函数的蓝图或者说公式。
1. 定义模板
比如说要写一个比较两数字大小的函数,由于数字类型可能不同,int 或者float等等,这是除了我们使用重载之外,使用模板会带来更大的便利。看下面:
// code 1
template <typename T>
int compare(const T& v1,const T& v2){
if(v1 < v2) return -1;
if(v1 > v2) return 1;
return 0;
}
当我们调用一个模板参数时,编译器(通常)用函数实参来为我们推断模板实参。即:当我们调用compare时,编译器使用实参的类型来确定绑定到模板参数T的类型。例如:
// code 2
cout<<compare(1,19)<<endl; //T 为int
接下来,就要说说模板类型参数了。
看下面两段小程序:
// code 3
template <typename T> T foo (T* p){
T tmp = *p;//tmp的类型将是指针p指向的类型
//......
return tmp;
}
这里提一下,typename和class没什么不同。不过typename更加直观一些~~
再看下面一段代码:
// code 4
template <unsigned N,unsigned M>
int compare(const char(&p1)[N],const char (&p2)[M]){
return strcmp(p1,p2);
}
上面的代码通过一个特定的类型名而非关键字class或typename来指定非类型参数。也就是在模板中定义非类型参数(nontype parameter),一个非类型参数表示一个值而非一个类型。当一个模板被实例化时,非类型参数被一个用户提供的或者编译器推断出来的值所代替。这些值必须是常量表达式,从而允许表一起在编译时实例化模板。
提示:
C++允许将变量定义成数组的引用,给予同样的道理,形参也可以是数组的引用。此时,引用形参绑定到对应的实参上,也就是绑定到数组上:
// code 5
void print(int (&arr)[10]){
for(auto elem:arr){
cout << elem << endl;
}
}
&arr两端的括号必不可少:
// code 6
f(int &arr[10]) //错误:将arr声明成了引用的数组
f(int (&arr)[10]) //正确:arr是具有10个整数的整型数组的引用
所以,使用非类型模板,可以让这个函数传递任意大小的数组。
2. 编写类型无关的代码
上面的code 1,其实对我们的类型要求还是很高的,也就是说这些类型必须支持<,同时也需要支持>。实际上,如果我们真的关心类型无关和可移植性,可能需要使用less来重新定义我们的函数:
//code 7
template <typename T>
int compare(const T& v1,const T& v2){
if(less<T>(v1 , v2)) return -1;
if(less<T>(v2 , v1) return 1;
return 0;
}
所以可以看出,模板程序应尽量减少对实参类型的要求。
再进一步,很多C++程序员更喜欢使用 != 而不喜欢<,原因也大概如此吧~~
3.模板编译
当编译器遇到一个模板定义时,他并不生成代码。只有当我们实力化出模板的一个特定版本时,编译器才会生成代码。当我们使用(而不是定义)模板时,编译器才会生成代码。这一特性影响了我们如何组织代码以及错误何时被检测到。
通常,当我们调用一个函数时,编译器只需要掌握函数的声明,类似的,当我们使用一个类类型的对象时,类定义必须是可用的,但成员函数的定义不必已经出现。因此,我们将类定义和函数声明放在头文件中,而普通函数和类的成员函数的定义放在源文件中。
模板则不同:为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非类型模板不同,模板的头文件通常既包括声明也包括定义。
通常,编译器会在三个阶段报告错误:
- 第一阶段实在编译模板本身时。在这个阶段,编译器通常不会发现很多错误。编译器可以检查语法错误。
- 第二个阶段是编译器遇到模板使用时。在此阶段,编译器仍然没有很多可检查的。对于模板的使用,编译通常会检查是参数目是否正确,还能检查参数类型是否匹配。对于类模板,编译器可以检查用户是否提供了正确数目的模板实参。
第三个阶段是模板实例化时,只有这个阶段才能发现类型相关的错误。依赖于编译器如何管理实例化,这类错误可能在链接阶段时才报告。
WARNING:
保证传递给模板的实参支持模板所要求的操作,以及这些操作在模板中能正确工作,是调用者的责任。~~
好了,函数模板就说这么多了,下一篇是 类模板~~