目录

本文是模板初阶文章,建议先学习下文,更有利于理解
【C++】是内存管理,但C++ !! && 模板初阶_花果山~~程序猿的博客-CSDN博客
我们回顾我们之前所使用的模板场景,大部分场景是作为类型模板。
- template < class T>
- 7 class arry{
- 8 private:
- 9 public:
- 10 T net;
- 11 };
但我们是否能回想起曾经的场景:
- 21 typedef N1 10;
- E> 22 typedef N2 10;
- E> 23 typedef N3 10;
- 24
- E> 25 int main()
- 26 {
- E> 27 int n1[N1];
- E> 28 int n2[N2];
- E> 29 int n3[N3];
- 30 return 0
- }
这个例子不是很贴切。需要定义多个宏,才能达到这样的效果,比较繁琐,而非模板参数可以解决这个问题
- 6 template < class T, size_t N = 20>
- 7 class arry{
- 8 private:
- 9 public:
- 10 T net[N];
- 11 };
- 12
- 13 int main()
- 14 {
- W> 15 arry<int, 10> n1;
- W> 16 arry<int, 10> n2;
- 17 return 0;
- 18 }
可见,非模板参数是可以设置缺省参数,但值得注意的是参数类型一定得是整型(布尔类型也可以,float,double不兼容),且N不允许修改。
总之:
补充:STL中的array接口代替原有数组,确实有全面检查越界好处,但不如直接用vector来的实在。
- // 函数模板 -- 参数匹配
- template<class T>
- bool Less(T left, T right)
- {
- return left < right;
- }
- int main()
- {
- cout << Less(1, 2) << endl; // 可以比较,结果正确
-
- Date d1(2022, 7, 7);
- Date d2(2022, 7, 8);
- cout << Less(d1, d2) << endl; // 可以比较,结果正确
-
- Date* p1 = &d1;
- Date* p2 = &d2;
- cout << Less(p1, p2) << endl; // 可以比较,结果错误
- return 0;
- }
可见p1, p2是指针,而这种类型无法使用此模板,因此我们需要进行模板特化。
即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。
1. 必须要先有一个基础的函数模板2. 关键字template后面接一对空的尖括号<>3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型4. 函数形参表必须要和模板函数的 基础参数类型 完全相同,如果不同编译器可能会报一些奇怪的错误
- // 函数模板 -- 参数匹配
- template<class T>
- bool Less(T left, T right)
- {
- return left < right;
- }
-
- // 全特化
- // 对Less函数模板参数类型进行指定一个唯一类型
- template<>
- bool Less
(Date* left, Date* right) - {
- return *left < *right;
- }
-
- // 偏特化
- template<>
- bool Less
-
- int main()
- {
- cout << Less(1, 2) << endl;
- Date d1(2022, 7, 7);
- Date d2(2022, 7, 8);
- cout << Less(d1, d2) << endl;
- Date* p1 = &d1;
- Date* p2 = &d2;
- cout << Less(p1, p2) << endl; // 调用特化之后的版本,而不走模板生成了
- return 0;
- }
上面的例子,就是为参数是指针对象进行比较。普通的模板无法比较,只能再写一个重载函数。
全特化即是将模板参数列表中所有的参数都确定化。
- // 类模板
- template<class T1, class T2>
- class Data
- {
- public:
- Data() {cout<<"Data
" <- private:
-
- T1 _d1;
- T2 _d2;
- };
- // 类模板全特化
- template<>
- class Data<int, char>
- {
- public:
- Data() {cout<<"Data
" <- private:
-
- int _d1;
- char _d2;
- };
2.偏特化(相当于服务一类人)
(1)部分特化
将模板参数类表中的一部分参数特化。
- // 类模板全特化
- template<>
- class Data<int, char>
- {
- public:
- Data() {cout<<"Data
" <- private:
-
- int _d1;
- char _d2;
- };
-
- // 类模板偏特化
- template<>
- class Data
char> - {
- public:
- Data() {cout<<"Data
" <- private:
-
- T _d1;
- char _d2;
- };
(2)参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数
更进一步的条件限制所设计出来的一个特化版本。
比如:指针类型,引用类型比较特别
- //两个参数偏特化为指针类型
- template <typename T1, typename T2>
- class Data
- {
- public:
- Data() {cout<<"Data
" <-
- private:
- T1 _d1;
- T2 _d2;
- };
-
- //两个参数偏特化为引用类型
- template <typename T1, typename T2>
- class Data
- {
- public:
- Data(const T1& d1, const T2& d2)
- : _d1(d1)
- , _d2(d2)
- {
- cout<<"Data
" <- }
-
- private:
- const T1 & _d1;
- const T2 & _d2;
- };
- void test2 ()
- {
- Data<double , int> d1; // 调用特化的int版本
- Data<int , double> d2; // 调用基础的模板
- Data<int *, int*> d3; // 调用特化的指针版本
- Data<int&, int&> d4(1, 2); // 调用特化的指针版本
- }
注意:当我们拷贝到编译器中时,发现我们运行不了这段代码,原因是什么呢?
原因:因为类模板的全特化,偏特化不是全新的模板,是需要存在基于原来的类模板为基础的代码,才能语法通过。

从结果来看,就像厨师做菜一样,为了保证出菜效率,我们会选择成品,半成品,原材料。全特化就是成品,偏特化则是半成品,原材料则是基础模
板。
三,模板分离编译
1.现象
测试一下场景,模板声明,定义分离在两个文件中的场景。
- // a.h
- template<class T>
- T Add(const T& left, const T& right);
- func();
-
- // a.cpp
- #inlclue "a.h"
- template<class T>
- T Add(const T& left, const T& right)
- {
- return left + right;
- }
-
- void func()
- {
- cout << "func" << endl;
- }
-
- // main.cpp
- #include"a.h"
- int main()
- {
- Add(1, 2);
- func();
- return 0;
- }
结果:你会发现,编译不通过。而我们注释掉main函数中的Add调用后,就能编译通过。
分析:
我们知道代码编译过程有,预处理(头文件展开,条件编译,剔除注释) ——> 编译(进行语法检查,生成语法树,生成汇编代码(给人看的)) ——> 汇编(生成机器码,注意:头文件不参与编译)——> 链接(根据修饰后的字符名,补充跳转地址)
其实是问题出在链接,文件之间在前3个过程,相互独立编译。编译时,普通函数可以生成地址,而模板因没有实例化所以无法生成地址,在链接时期,无法解决地址问题,所以无法编译成功。

啥?你问为啥模板不像普通函数一样,去其他文件中寻找定义并且实例化呢?
答:
先回答普通函数,普通函数的地址就是其函数定义代码的第一行地址,在链接时在函数声明处被填充,CPU可以快速找到。
而模板,因为链接时没有留下地址(因为T未实例化),得靠修饰名在大量的文件中寻找,本身这是一个很浪费资源的操作,所以编译器索性懒得寻找模板定义的位置,只允许模板定义与声明在同一个文件中。(方便查找到模板函数,并且实例化)
2. 解决方案
1.显示实例化(不推荐——治标不治本)
在模板定义位置,进行显示实例化。
- // a.cpp
- #inlclue "a.h"
- template<class T>
- T Add(const T& left, const T& right)
- {
- return left + right;
- }
-
- void func(){cout << "func" << endl;}
-
- template //2参数是小数
- double Add<double>(const double& left, const double& right);
-
- template // 2参数是整型
- int Add<int>(const int& left, const int& right);
-
- // main.cpp
- #include"a.h"
- int main()
- {
- Add(1, 2);
- func();
- return 0;
- }
缺陷是被动添加,每当出现一个新参数就需要添加,丧失模板的灵活性。
2.定义与声明头文件展开时在同一文件(最有效)
如小标题一样,定义与声明在头文件展开后要在同一文件中。
结语
本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力。
-
相关阅读:
预约小程序新选择:强大后端管理功能一览
Sql注入产生原因及威胁
(mac)Prometheus监控之Node_exporter(CPU、内存、磁盘、网络等)
C51单片机使用1-工程创建和Led闪烁灯
docker 中给命令起别名
小程序源码:人生重开模拟器
多线程-进阶
SpringBoot JavaBean对象拷贝 orika
Git实战技巧-查看不同版本之间的差异和代码的改动
Mac下XDebug安装
-
原文地址:https://blog.csdn.net/qq_72112924/article/details/132691299