定义模板
函数模板
template <typename T>
int compare (const T &v1, const T &v2){
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
当我们调用一个函数模板时,编译器用函数实参来为我们推断模板实参。
cout<<compare(1,0)<<endl;
除了定义类型参数,还可以在模板中定义非类型参数。一个非类型参数表示一个值而非一个类型。我们通过一个特定的类型名而非关键字class或typename来指定非类型参数
</font color=“red”>当一个模板被实例化时,非类型参数被一个用户提供的或编译器推断出的值所代替。这些值必须是常量表达式,从而允许编译器在编译时实例化模板。
例如:
template<unsigned N,unsigned M>
int compare(const char (&p1)[N],const char (&p2)[M]){
return strcmp(p1,p2);
}
编译器会使用字面常量的大小来代替N和M,从而实例化模板。
在模板定义内,模板非类型参数是一个常量值。在需要常量表达式的地方,可以使用非类型参数,例如,指定数组大小。
编写泛型代码的两个重要原则:
- 模板中的函数参数是const的引用
- 函数体中的条件判断仅使用<比较运算
通过将函数参数设定为const的引用,我们保证了函数可以用于不能拷贝的类型。大多数类型,包括内置类型和我们已经用过的标准库类型(除unique_ ptr 和I0类型之外),都是允许拷贝的。但是,不允许拷贝的类类型也是存在的。通过将参数设定为const 的引用,保证了这些类型可以用我们的compare函数来处理。而且,如果compare用于处理大对象,这种设计策略还能使函数运行得更快。
当编译器遇到一个模板定义时,它并不生成代码。只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。当我们使用(而不是定义)模板时,编译器才生成代码, .这一特性影响了我们如何组织代码以及错误何时被检测到。
通常,当我们调用一个函数时,编译器只需要掌握函数的声明。类似的,当我们使用一个类类型的对象时,类定义必须是可用的,但成员函数的定义不必已经出现。因此,我们将类定义和函数声明放在头文件中,而普通函数和类的成员函数的定义放在源文件中。
模板则不同:为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非模板代码不同,模板的头文件通常既包括声明也包括定义。
类模板
类模板与函数模板的不同之处是:**编译器不能为类模板推断模板参数类型。**为了啥用类模板,我们必须在模板名后的尖括号提供额外信息。
类模板与友元
//前置声明,在Blob中声明友元所需要的
template <typename>
class BlobPtr;
template <typename>
class Blob; // 运算符==中的参数所需要的
template <typename T>
bool operator==(const Blob<T>&, const B1ob<T>&) ;
template <typename T>
class Blob {
//每个Blob实例将访问权限授予用相同类型实例化的BlobPtr和相等运算符
friend class B1obPtr<T>;
friend bool operator==<T>(const Blob<T>&, const Blob<T>&);
};
类模板与static
类模板的每个实例都有一个独有的static对象。因此,与定义模板的成员函数类似,我们将static数据成员也定义为模板:
template<typename T>
size_t Foo<T>::ctr = 0; //定义并初始化ctr
模板实例化
函数实例化
//compare的特殊版本,处理字符数组的指针
template<>
int compare(const char* const& p1,const char* const& p2){
return strcmp(p1,p2);
}