C++ 模板进阶
一.非类型模板参数1.概念2.实例3.注意事项 二.模板的特化1.引出2.函数模板的特化1.语法和使用2.建议 3.类模板的特化1.全特化2.偏特化1.部分特化2.对参数进行进一步的限制 4.匹配顺序 三.模板的分离编译1.什么是分离编译2.模板的分离编译3.解决方法1.显式实例化(不推荐)2.分离编译放在头文件当中 四.模板的总结
一.非类型模板参数
1.概念
模板参数分为: 类型形参与非类型形参
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称后面
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用
2.实例
比方说我想实现一个静态的栈,栈的大小是10
#define N 10template<class T>class Stack{private:T _arr[N];};int main(){Stack<int> st1;Stack<string> st2;return 0;}
可是我需求变了,st1我想要10个大小,st2我想要200个大小,st3我想要1000个大小,st4我想要2个大小…
怎么办?
此时就需要用到非类型模板参数了
template<class T,size_t N>class Stack{private:T _arr[N];};int main(){Stack<int,10> st1;Stack<string,100> st2;Stack<char, 1000> st3;Stack<int*, 2> st4;return 0;}
此时你st1想要10个大小,st2想要100个大小,st3想要1000个…
都可以,完美的解决了这个问题
3.注意事项
浮点数、类对象以及字符串是不允许作为非类型模板参数的非类型的模板参数必须在编译期就能确认结果(因为模板是在编译阶段就要实例化的,而模板必须要知道实例化成的具体类型之后才能实例化,
因此非类型模板参数和类型模板参数一样,必须在编译阶段就能够确认实例化成的具体类型才可以,
否则编译阶段无法完成实例化,链接阶段就找不到实例化出的具体的类,进而发生链接错误)
二.模板的特化
1.引出
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板
我们写一个非常简易的日期类,用这个日期类开始下面的介绍
这里给出了头文件,至于源文件就不给大家了,毕竟我们这篇博客的目的也不是实现日期类
关于日期类的完善大家可以看我的这篇博客:
C++类和对象中:运算符重载+const成员函数+日期类的完善
对于第三个比较,我们想要比较的其实是p1和p2,可是实际比较的是p1和p2这两个指针
不符合我们的期望,怎么办呢?
2.函数模板的特化
1.语法和使用
因此我们就可以这样特化
// 函数模板 -- 参数匹配//基础的函数模板template<class T>bool Less(T left, T right){return left < right;}//函数模板的特化template<>bool Less<Date*>(Date* left, Date* right){return *left < *right;}
特化之后,如果函数模板的参数跟特化的类型是匹配的,那么就会走特化,并不会走函数的基础模板
跟我们之前学的函数模板推演实例化时如果有更匹配的会优先去匹配那个更匹配的,这个原则是一样的
2.建议
不建议大家使用函数模板的特化,而建议大家直接写一个同名的非函数模板即可,
因为:
1.函数模板的特化和直接写一个同名的非模板函数的工作量是一样的
2.函数模板的特化的要求更多
比如:必须要有一个基础的函数模板
而且函数形参表: 必须要和模板函数的基础参数类型完全相同
3.而且因为const,引用和指针的关系,导致函数模板的特化变得更加复杂
4.直接写一个同名的非函数模板:简单明了,代码的可读性高,容易书写
3.类模板的特化
1.全特化
全特化: 将模板参数列表中所有的参数都确定化
语法跟函数模板的特化非常相似
都是需要一个基础模板等等…
template<class T1,class T2>class C{public:C(){cout << "C<T1,T2>" << endl;}};template<>class C<int,double>{public:C(){cout << "C<int,double>" << endl;}};
2.偏特化
偏特化:任何针对模版参数进一步进行条件限制而设计的特化版本
偏特化有两种表现形式:
部分特化和对参数进行进一步限制
偏特化也要求必须要有基础模板
1.部分特化
比如说特化第一个参数是int
template<class T1,class T2>class C{public:C(){cout << "C<T1,T2>" << endl;}};template<>class C<int, double>{public:C(){cout << "C<int,double>" << endl;}};template<class T>class C<int,T>{public:C(){cout << "C<int,T>" << endl;}};int main(){C<int, double> c1;C<int, char> c2;C<char, int> c3;return 0;}
2.对参数进行进一步的限制
偏特化并不仅仅是指特化部分参数,也可以用来对参数进行进一步的限制
针对于我们一开始引入的比较Date*的例子,我们可以使用类模板来解决这个问题
template<class T>class D{public:bool less(const T& a, const T& b){return a < b;}};//偏特化一个专门用于指针比较的类template<class T>class D<T*>{public:bool less(const T* a,const T* b){return *a < *b;}};
int main(){cout << D<int>().less(2, 1) << endl; // 可以比较,结果正确Date d1(2024, 8, 5);Date d2(2024, 8, 10);cout << D<Date>().less(d1, d2) << endl; // 可以比较,结果正确Date* p1 = &d1;Date* p2 = &d2;cout << D<Date*>().less(p1, p2) << endl; // 可以比较,结果正确return 0;}
注意:这里不建议在偏特化指针类型时加上&
bool less(const T* const& a,const T*& b)
因为如果加上了&,就容易出现这种错误
此时只需要在引用前面加上const即可
bool less(const T* const& a,const T* const& b)
4.匹配顺序
模板参数的匹配原则:
会优先匹配更匹配的
如果跟全特化匹配,就匹配全特化
否则如果跟偏特化更匹配,就匹配偏特化
如果跟偏特化也不匹配,就匹配原模板
三.模板的分离编译
1.什么是分离编译
2.模板的分离编译
我们知道一般的类或者函数都是可以分离编译的,
短小,简单的函数定义为内联函数放在头文件当中
其他函数仅仅将声明放在头文件,定义放在源文件当中
对于带有模板的类或者函数,支持分离编译吗?
下面我们以函数模板为例,演示一下:
例如下面这份程序,对非模板函数f1和模板函数f2进行了分离编译
一编译,结果发现连接错误,原因是找不到模板函数f2的地址
为什么会这样呢?
下面我们来分析一下
首先,经过之前的学习,我们知道:
今天我们需要留意的是:
因此我们可以得出如下结论:
3.解决方法
1.显式实例化(不推荐)
在定义该函数的源文件当中进行显式实例化
func.cpp//显式实例化templatevoid f2<int>(const int& a);//f2的定义template<class T>void f2(const T& a){cout << "f2<T>" << endl;}
不过这种方法并不好用,甚至非常难用
比如,我现在不想f2(1)这么调用了
我想f2(1.1)这么调用,让T实例化为double
此时又链接错误了…
2.分离编译放在头文件当中
那么怎么办呢?
要不然干脆不进行声明跟定义分离,这样当然可以
要不然声明跟定义分离,但是将定义也放在头文件当中
这样的话,用声明的地方一定有定义
编译阶段就能够正常实例化出模板参数的类型了
通常情况下,这种包含模板的头文件习惯性命名为.hpp文件
当然命名为.h文件也是可以的
这种时候你想怎么玩就怎么玩
四.模板的总结
以上就是C++ 模板进阶的全部内容,希望能对大家有所帮助!