模板的概念
- 模板是泛型编程的基础,根据具体类型在编译时生成实例类;
- 独立于任何特定类型编写的通用程序;
- C++中包含函数模板与类模板;
- 模板定义并不会使编译器产生相应的代码,只有在调用时编译器才会特化出这个版本;
- 通常定义在.h文件中
函数模板
定义
template <typename T>
T func(T a,T b)
{
return a+b;
}
- template后跟参数列表,如果有多个用”,”隔开,尖括号里可以是typename,也可以是class,甚至可以混着用;
- 使用的时候有时需要显式指定类型,有时可根据数值进行推断;
调用
函数模板的调用与普通函数没有区别,因为他是根据实参推断类型,不需要我们显式指定;
非类型模板参数的使用
Templa的尖括号中不仅可以给类型参数,还可以给非类型参数,如:
template <int a,int b>
int func(){
return a+b;
}
调用的时候:
func<12,13>();//要注意这里<>里边必须是常量或常量表达式,以确保在编译阶段就能确定版本;
类型参数与非类型参数混合使用
template <typename T,int a,int b>
int func(T c){
return (int)c+ a+b;
}
func<int,12,13>(5);
类模板
- 编译器不能为类模板推断模板参数类型,需要显式指定;
- 同函数模板,实现与类型无关的抽象逻辑;
定义
template <typename T1,typename T2>
class ClassName{
...
}
由于实例化一个具体类的时候需要类模板的所有信息,因此类模板的声明,定义,实现等都要放在.h文件里完成,不像一般的类实现在cpp中;其他要使用这个类模板的直接#include即可;
类模板成员函数
直接写在类模板的定义的时候,成为隐式inline;当然也可以声明与定义分开来写,但是都要在.h文件中;
与一般成员函数的区别在于,类模板的成员函数具有模板参数:
先声明后定义的写法:
声明:
public:
void myfunc();
定义:
template <typename T>
void myvector<T>::myfunc(){...}
总结:这里要将“类模板名<类型>”当做普通类名来使用就对了;
类即使被实例化,但是它的成员函数也只有在调用的时候才会进行实例化;
类模板名使用
在类模板内部使用类模板名,是否紧跟<参数模板>是等同的,也就是说在内部使用可以直接使用不带参数模板的类模板名;
但是在类外使用,一定是带<>才能等同,相当于普通类的使用方法;
非类型模板参数的使用
与函数模板类似,也有非类型的模板参数,使用的时候注意尖括号里边的内容:
定义:
template<typename T,int size=10>//还能提供缺省值
class myarray{
T arr[size];
}
使用:myarray<int,100> Arr;
myarray<int> Brr;//使用缺省值
要注意此时实例化的类名要包含<>,因此类外实现成员函数的写法:
template<typename T,int size>
void myarray<T,size>::func(){}
注意:
- double一般不能作为非类型模板参数;
- 类类型也不能作为非类型模板参数;
typename的使用场合
- 定义模板的时候放在<>中,可以使用class置换;
- 显式的说明后跟一个类型,不能使用class置换:
默认模板参数
类模板
- 如果一个模板参数具有默认值,则其右侧所有参数都要有默认值,与函数默然参数规定一致;
- 如果都是用模板参数,使用的时候直接使用<>
普通类成员函数模板
就是普通类的成员函数定义为函数模板:
类模板的成员函数模板
全特化与偏特化
所谓特化是泛化的对立面,泛化讲究不固定于特定类型,但是有的时候针对有的类型不能对模板实例化(编译出错),此时就要提供一个对特定类型的特化版本,这就叫做特化;
类模板特化
全特化
特化是相对于泛化的,得先有泛化版本才能有对应的特化版本作为补充;因此放置位置一般泛化特化都在一个.h中,且特化放在泛化后边一点;
泛化版本:
template <typename T,typename U>
struct TC{
...
};
特化版本:
template <>
struct TC<int,double>{
...
};
特化版本执行的优先级高于泛化版本,也就是说如果能找到完全匹配的特化版本是不会调用泛化版本成员与函数的;
全特化类模板成员函数
template<>
void TC<int,double>::func(){...}
要注意全特化就是带上template<>,然后在合适的位置显式指定类型;
偏特化
这是一个与全特化对立的概念,意思就是局部特化,不全,一半显式指定(特化),一半依赖泛化;
数量上的偏特化:
1.没有特化的部分保留在template中;
2.要保留特化与泛化的顺序;
参数范围上的偏特化
参数范围:int与const int,T与T*,这样的类型范围实际是变化了,这就是参数范围的概念;
- template<>里的类型参数要保留;
- 对泛化类型进行范围限制
函数模板特化
全特化
template <typename T,typename U>
void func(T& t,U& u)
{
...
}
template <>
void func(double& t,int& u)
{
...
}
与类相似,保留template<>但是空置参数列表,在后续指定特化;
函数还涉及一个重载的问题,比如还定义了一个普通函数func(double& t,int& u);那么要注意优先顺序:重载普通函数>特化函数>泛化函数
偏特化
不好意思,函数模板不能偏特化(真是太好了……)
特殊用法
using
使用using可以简化长模板名取别名的问题;
显式指定模板参数
从左到右依次指定或者推断,从左到右如果省略了一个参数的指定,那后边也要都省略,类似于指定默认值从左到右都要(坑真多)!