C模版进阶全知道
【C++】模版进阶全知道
🍃 本系列为初阶C++的内容,如果感兴趣,欢迎订阅🚩
小编会用最简单最生动的比方,让大家学懂
🎊个人主页:[小编的个人主页])
🎀 🎉欢迎大家点赞👍收藏⭐文章
✌️ 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍
🐼前言
我们知道,函数模版是 泛型编程 的一种思想, 通过函数模版,我们无需再重复实现一些逻辑相似的代码,而是通过函数模版进行复用,通过实例化出不同的类,达到对应的目的。而有些场景对于一些复杂的类型,比如指针,引用等,贸然使用函数模版,可能达不到我们想要的效果,就比如,函数模版就像一个通用的 “工具箱” ,里面有各种工具(通用模板)。但是当你需要处理一个特殊的任务(比如打印字符串时加上引号),你专门准备了一个 定制化 的工具, 这个工具只针对这个特殊任务,效果更好 。这就是我们将学习的类特化。
🐼函数模版特化
我们先来看这样一个例子,比如实现一个泛型的比较函数,然后我们来比较内置类型, 日期类,指针等 。
template<class T> bool cmp(T left, T right) { return left < right; } class Date { friend ostream& operator<<(ostream& _cout, const Date& d); public: Date(int year = 1900, int month = 1, int day = 1) : _year(year) , _month(month) , _day(day) {} bool operator<(const Date& d)const { return (_year < d._year) || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && _day < d._day); } bool operator>(const Date& d)const { return (_year > d._year) || (_year == d._year && _month > d._month) || (_year == d._year && _month == d._month && _day > d._day); } //private: int _year; int _month; int _day; }; ostream& operator<<(ostream& _cout, const Date& d) { _cout << d._year << "-" << d._month << "-" << d._day; return _cout; } int main() { cout << cmp(1, 2) << endl;//正确 Date d1(2025,3,5); Date d2(2025,3,1); cout << cmp(d1, d2) << endl;//正确 Date* d3 = new Date(2025, 3, 5); Date* d4 = new Date(2025, 3, 1); cout << cmp(d3, d4) << endl;//错误,不确定 return 0; }
我们发现,比较一些内置类型以及自定义类型(只要自定义类型重载了自已的比较逻辑)好像都没有什么问题,但是如果参数是Date的指针,这样再套用com比较就有问题了,因此 指针在内存中分配的地址是不确定的 ,每次编译时,分配的地址是随机的。我们打印出来的结果也总是不确定的,因此,我们需要专门写一个 **Date的比较逻辑基于cmp** ,这就是函数的特化。 这个函数专门用于比较日期类的指针!!!,为他量身定制的。
//cmp的函数模版特化 template<> bool cmp<Date*>(Date* left, Date* right) { return *left < *right; }
特化的格式,template<>里面什么都没有,特化的标志, 在
函数名
后面跟上你要特化的类型。
但是我们 不常用函数模版的特化
,因为我们完全可以再重载一个比较逻辑,不需要特化:
bool cmp(Date* left, Date* right) { return *left < *right; }
🐼类模版特化
类模版特化又分别, 全特化和偏特化
全特化简单理解就是 所有参数都确定化 ,已经确定了:
比如:
template<class T1,class T2> class car { public: car() { cout << "car<T1, T2>" << endl; } private: T1 _d1; T2 _d2; }; //全特化 template<> class car<string,int> { public: car() { cout << "car<string,int>" << endl; } private: string _d1; int _d2; };
两个模版参数特化时都确定了。( 相当于给一个人定制化的完完全全 )
偏特化是 模版参数一部分特化或者是针对模版参数进一步限制 :
下面的例子:
//偏特化 template<class T1> //部分参数特化 class car<T1, int> { public: car() { cout << "car<T1, int>" << endl; } private: T1 _d1; int _d2; }; //参数进一步限制 template<class T1, class T2> class car<T1*, T2*> { public: car() { cout << "car<T1*, T2*>" << endl; } private: T1* _d1; T2* _d2; }; template<class T1, class T2> class car<T1&, T2&> { public: car() { cout << "car<T1&, T2&>" << endl; } private: const T1& _d1 =T1(); const T2& _d2 =T2(); }; int main() { car<int, double > c0; car<int, int> c1; car<string, int> c2; car<int*, int*> c3; car<int&, int&> c4; return 0; }
输出结果:
由此知道,编译器在类模版参数特化时,有现成吃现成(即有对应的特化),没有现成编译器才需要实例化 。 相等于给一个人部分化定制
🐼模版的分离编译
我们知道,一个源文件生成可执行程序分别要经过 预处理,编译,汇编,链接 四个大阶段。
一个程序可能由多个源文件构成,而每个源文件 单独编译形成目标文件 ,链接时将 所有源文件链接起来共同形成一个可执行程序 。
分离编译是指将程序的不同部分编译成独立的对象文件(.o 或 .obj),然后在链接阶段将这些对象文件组合成最终的可执行文件。分离编译要求每个编译单元(.cpp 文件)在编译时是 独立 的,编译器无法看到其他编译单元的内容。
也就是编译形成obj文件,就是 自已玩自已的 (都是独立的),等到链接时,把大家都链接起来。
先说结论,模版的声明和定义分开,即在头文件中声明,在源文件中定义,这会引发 链接错误 。
原因:汇编完后的多个obj目标文件, 由于函数模版无法实例化,因此无法放入到函数表中 ,链接时由于 寻址错误 ,就会链接失败。
即模板的实例化需要编译器在编译时知道模板的具体实现细节。如果模板的定义和实例化分布在不同的编译单元中,编译器在编译其中一个单元时可能无法看到模板的完整定义,从而无法生成正确的代码。
比如,我在Myfile.h头文件中声明函数模版,在file中定义。
“Myfile.h”
#include<iostream> using namespace std; template<class T> void func2(const T& x);
“file1.cpp”
template<class T> void func2(const T& x) { cout << x << endl; }
这时候会报出链接错误。
因此, 模版是不支持声明和定义分离的 。
解决办法:
1.将声明和定义放在一起
“Myfile.h”
template<class T> void func2(const T& x) { cout << x << endl; }
方法2:在定义时显示实例化
template<class T> void func2(const T& x) { cout << x << endl; } template void func2<int>(const int& x);//显示实例化
这样,编译器在链接时就不会根据无法实例化而报出链接错误
感谢你耐心地阅读到这里,你的支持是我不断前行的最大动力。如果你觉得这篇文章对你有所启发,哪怕只是一点点,那就请不吝点赞👍,收藏⭐️,关注🚩吧!你的每一个点赞都是对我最大的鼓励,每一次收藏都是对我努力的认可,每一次关注都是对我持续创作的鞭策。希望我的文字能为你带来更多的价值,也希望我们能在这个充满知识与灵感的旅程中,共同成长,一起进步。再次感谢你的陪伴,期待与你在未来的文章中再次相遇!⛅️🌈 ☀️