非类型模板参数:
C++中绝大多数地模板参数都是类型模板参数,其实模板的参数分为两种,类型参数和非类型参数,所谓非类型参数通常用于初始化空间所用,举个例子。
假设我们要定一个内部属性是静态数组的对象,我们最容易想到的就是
#define N 10
template<typename T>
class Array{
T arr[N];
};
这样写是没有语法错误的,但是我们在实例化对象的时候会发现所有对象中的数组大小只有N,无法灵活调整。
要想要每一个对象中的数组大小可控,非类型参数就起了很大的作用,
C++规定,非类型参数只能是整型值(int,size_t,long,long long,short,bool,char)
template<typename T,size_t N>
class Array{
T arr[N];
};
如此一来就可以在定义对象的时候自由规定长度
Array<int,10> a1;
Array<float,5> a2;
模板特化:
首先引入日期类以及仿函数
class Date{
public:
Date(int year,int month,int day)
:_year(year)
,_month(month)
,_day(day)
{}
bool operator == (const Date& v)const{
return _year==v._year&&_month==v._month&&
_day==v._day;}
private:
int _year;
int _month;
int _day;
};
template<typename T>
struct Equal{
bool operator()(T& x , T& y){
return x==y;}
};
vector< Date > v1;
v1.push_back(Date(2024,2,9));
v1.push_back(Date(2023,2,9));
cout<<Equal(v1[0],v1[1]);
引用部分的意图是将下标为0和1的两个元素作比较,通过运行可以发现结果是符合预期的,but如果现在我有一个元素是Date* 类型的vector对象,那么最终输出的结果是随机的
vector<Date*> v2;
v2.push_back(new Date(2024,2,9));
v2.push_back(new Date(2023,2,9));
cout<<Equal(v2[0],v2[1]);
在调用仿函数Equal时其实对指针进行了比较,在堆上开辟出的位置时随机的,故结果也是随机的,但显然这是不符合预期的,我们需要一份专门Date*类型设计的Equal
于是就有了模板特化
template<>
Equal<Date*>{
bool operator == (const Date* p)const{
return *this==*p;
}
};
如此类具有template<>的特化我们称之为全特化,与之对应的则是偏特化,即对模板参数的一部分作出限定,有兴趣的读者可自行查阅,这里不做过多解释。
模板分离编译:
在模拟实现STL中的各种容器以及适配器的过程中,细心的小伙伴可能已经留意到了我们基本上都是把所有代码放在一个头文件中去实现,这与我们平时的规范貌似有点不符,的确,但是也是无奈之举,如果不这么,编译器会发生链接错误,下面解释具体原因。
c语言阶段我们已经知道编译链接的过程可以细分为预处理、编译、汇编、链接四个部分,每一个源文件是独立编译成二进制文件后链接成为一个可执行程序的。
对于头文件只是简单的展开,如果遇到函数声明则会先产生一个无效地址暂作标记,在链接的时候重定位到有效位置。
举个例子:
//fun.h
void fun1(int x,int y);
template<typename T>
void fun2(T x, T y);
//fun.cpp
#include "fun.h"
void fun1(int x,int y){
cout<<x<<y<<endl;}
void fun2(T x,T y){
cout<<x<<y<<endl;}
//main.cpp
#include "fun.h"
int main(){
fun1(1,1);
fun2(1,1);
return 0;
}
编译运行上述代码文件,发现是通不过的,而屏蔽掉fun2语句就可以通过,为什么呢?
原因在于当两个源文件各自编译的时候,main.cpp中遇到fun1、fun2声明时,会先产生一个无效地址标记,fun.cpp编译时对于fun1已经明确知道了fun1所需要的栈帧空间大小,就会产生一个有效地址进入符号表,而对于fun2,编译器完全不知道T是什么,也就不能确定大小,产生不了有效地址,最后在链接的时候fun1可以成功被调用,但是fun2编译器不知道其所在的地址,链接错误就这么发生了。
因此模板是不建议声明和定义分离的,声明和定义写入同一个头文件无疑是最好的选择,这样编译器在编译的时候就可以识别T代表什么类型,就能够正常生成有效地址。