定义模板
- 我们可以像这样定义一个函数模板
template<class T>
int compare(const T&a, const T&b) {
if (a < b) return 1;
else return 0;
}
当我们调用模板时,编译器使用函数实参为我们推断出模板实参。编译器用模板推断出的模板参数来为我们实例化一个特定版本的函数。
//对上面的函数,我们给出下面的调用
//实例化出compare(const int &a,const int &b);
compare(1, 2);
//实例化出compare(const vector<int>&a,const vector<int>&b);
vector<int> a({ 1, 2, 3 });
vector<int> b({ 2,3,4,5 });
compare(a, b);
- 模板类型参数
我们的compare有一个模板类型参数T,我们可以将其看作类型说明符,就像内置类型和类类型说明符一样使用,特别的,类型参数可以用来指定返回类型与函数参数类型,以及用于函数体内变量声明和类型转换。
非模板类型参数
除此之外,我们还可以定义非模板类型参数,其表示一个值而非一个类型。需要注意的是,非类型参数被用户提供或编译器推断出的值所替代,这些值必须是常量表达式(值不会改变且在编译期间就能得到结果的表达式),才可以使编译器在编译期间实例化模板。且看下面的例子
template<unsigned N,unsigned M>
int compare(const char(&p1)[N], const char(&p2)[M]){
return strcmp(p1, p2);
}
//用户代码
compare("hi", "haha");
调用模板函数后,编译器会用字面常量的大小来替代N,M,从而实例化模板。编译器会实例化出如下版本。
//字符串字面常量末尾会被插入一个空字符
int compare(const char(&p1)[3],const char(&p2)[5]);
一个非类型参数可以是一个整型,或者是一个指向对象或函数的类型的指针或左值引用。绑定到非类型整型参数的实参必须是一个常量表达式。绑定到指针或引用非类型参数的实参必须具有静态生存期(生存期与程序生存期相同,包括局部static对象,类static对象,定义于任何函数之外的变量)。指针参数也可以用NULL或值为0的常量表达式来实例化。
-
模板编译
编译器在遇到一个模板时,并不会生成代码,只有在需要实例化一个模板的特定版本时候,编译器才会生成代码。
通常,在我们使用一个函数时,编译器只需要掌握函数声明,当我们使用一个类类型时,类的定义必须时可用的,但其成员函数不必已经出现。因此,我们可以把函数声明,类定义,类成员函数声明放在头文件中。函数定义,类成员函数定义放在源文件中
模板规则则不同,为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,模板的头文件通常既包含声明也要包含定义。(感谢前几天的那个BUG让我真正的理解了这句话。) -
类模板用于生成类的蓝图,区别于函数模板,编译器不能为类模板推断参数类型,所以我们在使用类模板时必须在模板名后提供额外的信息。如:
vector<int> ame
类似函数模板,类模板在使用过程中同样会实例化。对于每种元素类型,编译器会生成不同的类。
template<class T>
class Blob{
//........
}
//实例化出两个类。
Blob<string> ame;
Blob<int> ori;
类模板的成员函数定义在模板类内的,被隐式声明为内联函数。类模板的成员函数本身也是普通函数,但是因为类模板的每个实例都有自己版本的成员函数,因此,类模板的成员函数具有和类模板相同的模板参数。
类代码内简化模板类名的使用
template<class T1,class T2>
struct tes{
pair& operator++();
//等价于
pair<T1,T2>& operator++();
};