目录
一.非类型模板参数
1.概念
2.使用
3.常见错误
二.模板的特化
1.什么是模板的特化
函数模板特化
类模板特化
2.全特化和偏特化
全特化
偏特化(半特化)
三.模板的分离问题
本质: 用一个常量做为类(函数)模板的参数, 同时可以把它当作常量来使用
注: 非类型模板参数只能是整型家族(size_t/ int/ char)
template
void test1(T1 t)
{
cout << "t: " << t << endl;
cout << "size_t N: " << N << endl;
}
template
void test2(T2 t)
{
cout << "t: " << t << endl;
cout << "size_t N: " << N << endl;
}
//模板进阶
int main()
{
//一.非类型模板参数
int a = 20;
//1.显式传入N的大小(注意只能传入常量, 并不能传入变量)
test1(a);
//2.可以给N缺省值
test2(a);
return 0;
}
非类型模板参数的具体使用场景
在学习非类型模板参数之前我们只能用#define N 5或者 const N = 5这样的方式来定义宏N, 这个N在每次实例化的时候值不会改变, 但是引入非类型模板参数之后, 我们每次实例化的时候, 都可以显示去传一个非类型模板参数, 从而每次控制N的大小, 这个N如果不需要改变的话我们也没, 必要每次都显式的传, 此时我们就可以给N一个缺省值
template
class my_test
{
public:
void print()
{
cout << "_array_size: " << N << endl;
}
private:
T3 _array[N];//这个数组每次实例化模板类可以不定长度
};
int main()
{
my_test myt1;
myt1.print();
my_test myt2;
myt2.print();
return 0;
}
1).
2).
3).
本质: 对于特殊的模板类型做出特殊的处理, 这个特殊处理是你自己指定的
对于int*做出了特殊处理, 当模板类型推导出的是int*时, 单独走int*的函数逻辑
为什么要这么做?
如果我现在要比一对数据, 直接比他们的值是没有问题的, 但是如果我给出这两块待比较数据的指针呢? 指针可不能直接用大于/小于来比较, 需要先解引用再进行比较, 所以以下拿int*来做为例子
这时会有人问, 那么每次保证传入的都是数据的值不就好了吗?我在传入的时候, 就传入*pa/*pb, 这样不就可以直接比较了?
这显然不是一种好的解决方式, 比如priority_queue用的是仿函数less/greater来比较的, 比如现在有一个日期类, 我可以在优先级队列存data类型的数据, priority_queue, 那如果我要存入的数据是需要new出来的, 我就要在优先级队列存入data*类型的数据, priority_queue, 这时我们想要比的是data的数据, 而并非data的地址, 我们就要用模板的特化来处理这类问题, 给less/greater一个针对于T*的独特处理方式(这属于偏特化)
template
bool my_less(T l, T r)
{
return l < r;
}
//模板的特化
template<>
bool my_less(int* l, int* r)
{
return *l < *r;
}
//----------------------------------------
int a = 10;
int b = 20;
if (my_less(a, b))
{
cout << "T: int -- a < b " << endl;
}
else
{
cout << "T: int -- a > b " << endl;
}
int* pa = &a;
int* pb = &b;
if (my_less(pa, pb))
{
cout << "T: int* -- a < b " << endl;
}
else
{
cout << "T: int* -- a > b " << endl;
}
template
class A
{
public:
void print()
{
cout << "A" << endl;
}
private:
K1 a;
};
template<>
class A
{
public:
void print()
{
cout << "A" << endl;
}
private:
char b;//可以省略
};
A a;
a.print();
A b;
b.print();
全特化就是将所有的模板参数全部特化, 上面的例子就是一个偏特化, 因为他只有一个模板参数, 以下我们再举例一个
给出两个模板参数T1和T2, 全部特化为int, double
必须要两个参数与int, double严格匹配, 才可以调用到全特化的模板函数
//函数模板全特化
template
void my_test1(T1 a, T2 b)
{
cout << "调用模板函数my_test1" << endl;
}
//全特化
template<>
void my_test1(int a, double b)
{
cout << "调用全特化模板函数my_test1" << endl;
}
//可以写成void my_test1(int a, double b)但这就不是模板的特化,而是定义了函数
my_test1(10, 20);//(int, int)
my_test1(10.10, 20.20);//(double, double)
my_test1('a', 20.20);//(char, double)
my_test1(10, 20.20);//(int, double)
//类模板全特化
template
class B
{
public:
void print()
{
cout << "A" << endl;
}
private:
K3 a;
K4 b;
};
template<>
class B
{
public:
void print()
{
cout << "A" << endl;
}
private:
char a;//可以省略
int b;//可以省略
};
B c;
c.print();
B d;
d.print();
偏特化可以理解为将部分模板参数特化, 但不止于此
注: 函数模板不支持偏特化, 但是可以写为函数模板重载
//函数模板不支持偏特化, 但是可以写为函数模板重载
template
void MyTest(M1 a, M2 b)
{
cout << "" << endl;
}
template
void MyTest(M1 a, char b)
{
cout << "" << endl;
}
template
void MyTest(M1* a, M2* b)
{
cout << "" << endl;
}
int a = 10;
int b = 20;
MyTest(10, 20);
MyTest(10, 'a');
MyTest(&a, &b);
//类模板偏特化
template
class C
{
public:
void print()
{
cout << "" << endl;
}
};
//偏特化(部分模板参数特化)
template
class C
{
public:
void print()
{
cout << "" << endl;
}
};
//偏特化(对于推导的K5K6的类型, 特定形式发生特化)
template
class C
{
public:
void print()
{
cout << "" << endl;
}
};
C().print();
C().print();
C().print();
C().print();
C().print();
C().print();
总结: 函数模板只能全特化, 类模板可以全特化/偏特化, 函数模板可以用重载的方式来代替偏特化
模板不可以分文件编写, 一但分文件编写, 就会报出链接错误
为什么会有链接错误?
因为对于模板而言, 只是一种定义规范, 并没有真正的定义, 而是在实例化的时候, 才会去真正的定义, 所以如果分文件编写, 在包含了头文件之后, 就只有声明而并不能找到实现, 因为模板是在实例化的时候才去定义, 所以模板定义的函数不会写入符号表, 也就不会被声明找到, 从而引发链接错误
模板定义的函数实例化后依旧不会写入符号表
模板必须把定义和声明写在一个文件(在文件内可以声明与定义分离), 所以不存在需要链接的场景, 每次使用这个模板时包含头文件, 每次包含头文件就同时包含了声明与定义, 所有模板实例化后定义的函数就没有必要写入符号表, 由于在同一文件, 故在编译阶段就已经确定地址了!
...
有一种方式可以让模板分文件编写, 但不推荐!!这里只是知道一下而已, 正常来说不会这么写
实现了模板分文件编写后, 在模板的定义的那个文件最后显式实例化
拿vector类模板举例
//vector的push_back, pop_back...的定义
//......
//......
//显式实例化
//用到哪个实例化模板类就显式写哪个
//比如我要用到vector和vector
//以下我就需要写
template
vector
template
vector