C++之模版进阶篇(上):https://developer.aliyun.com/article/1625008
2.3 类模板特化
2.3.1 全特化和偏特化
全特化即是将模板参数列表中所有的参数都确定化。
偏特化:任何针对模版参数进一步进行条件限制设计的特化版本。
template<class T1, class T2> class Data { public: Data() { cout << "Data<T1, T2>" << endl; } private: T1 _d1; T2 _d2; }; //全特化 template<> class Data<int, char> { public: Data() { cout << "Data<int, char>" << endl; } private: int _d1; char _d2; }; //偏特化 template<class T1> class Data<T1, int> { public: Data() { cout << "Data<T1, int>" << endl; } private: T1 _d1; int _d2; }; int main() { Data<int, int>d1; Data<int, char>d2; Data<int, int> d3; return 0; }
偏特化有以下两种表现方式:
部分特化
将模板参数类表中的一部分参数特化。
// 将第二个参数特化为int template <class T1> class Data<T1, int> { public: Data() {cout<<"Data<T1, int>" <<endl;} private: T1 _d1; int _d2; };
参数更进一步的限制
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本
/两个参数偏特化指针类型 //template<class T1,class T2> template<typename T1, typename T2> class Data<T1*, T2*> { public: Data() { cout << "Data<T1*, T2*>" << endl; } private: T1 _d1; T2 _d2; }; //两个参数特化引用类型 template<class T1, class T2> class Data<T1&, T2&> { public: Data() { cout << "Data<T1&, T2&>" << endl; } private: T1 _d1; T2 _d2; }; //一个指针一个引用 template<class T1, class T2> class Data<T1&, T2*> { public: Data() { cout << "Data<T1&, T2*>" << endl; } private: T1 _d1; T2 _d2; };
2.3.2类模版特化应用实例
实例1:
class Date { public: Date(int year = 1, 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); } friend ostream&operator<<(ostream& out, const Date& d) { out << d._year << "-" << d._month << "-" << d._day; return out; } private: int _year; int _month; int _day; }; /* template <class T> bool Less(const T &left,const T& right) { return left < right; } */ template<class T> struct Less { bool operator()(const T& x, const T& y) const { return x < y; } }; int main() { vector<Date>d1; d1.push_back(Date(2024, 8, 25)); d1.push_back(Date(2024, 8, 24)); d1.push_back(Date(2024, 8, 27)); sort(d1.begin(), d1.end(), Less<Date>()); for (const auto& date : d1) { cout << date << endl; } return 0; }
实例2:
#include <iostream> #include <vector> #include <algorithm> using namespace std; class Date { public: Date(int year = 1, 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); } friend ostream&operator<<(ostream& out, const Date& d) { out << d._year << "-" << d._month << "-" << d._day; return out; } private: int _year; int _month; int _day; }; template<class T> class Less { public: bool operator()(const T& x, const T& y) const { return x < y; } }; // 对Less类模板按照指针方式特化 /* template <class T> bool Less(const T &left,const T& right) { return left < right; } */ int main() { Date d1(2022, 7, 7); Date d2(2022, 7, 6); Date d3(2022, 7, 8); vector<Date> v1; v1.push_back(d1); v1.push_back(d2); v1.push_back(d3); // 可以直接排序,结果是日期升序 sort(v1.begin(), v1.end(), Less<Date>()); for (const auto& date : v1) { cout << date << endl; } vector<Date*> v2; v2.push_back(&d1); v2.push_back(&d2); v2.push_back(&d3); // 可以直接排序,结果错误日期还不是升序,而v2中放的地址是升序 // 此处需要在排序过程中,让sort比较v2中存放地址指向的日期对象 // 但是走Less模板,sort在排序时实际比较的是v2中指针的地址,因此无法达到预期 sort(v2.begin(), v2.end(), Less<Date*>()); for (const auto& date : v2) { cout << *date << endl; } return 0; }
通过观察上述程序的结果发现,对于日期对象可以直接排序,并且结果是正确的。但是如果待排序元素是指针,结果就不一定正确。因为:sort 最终按照 Less 模板中方式比较,所以只会比较指针,而不是比较指针指向空间中内容,此时可以使用类版本特化来处理上述问题:
// 对Less类模板按照指针方式特化 template<> struct Less<Date*> { bool operator()(Date* x, Date* y) const { return *x < *y; } };
3.模版分离编译
3.1 什么是分离编译
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
3.2 模板的分离编译
假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义。
// a.h template<class T> T Add(const T& left, const T& right); // a.cpp template<class T> T Add(const T& left, const T& right) { return left + right; } // main.cpp #include"a.h" int main() { Add(1, 2); Add(1.0, 2.0); return 0; }
C/C++程序的运行过程通常包括以下几个步骤:
1. 预处理(Preprocessing):
- 预处理器处理源代码文件中的预处理指令,如 `#include`、`#define`、`#if`、`#ifdef` 等。
- 预处理器将包含的文件内容插入到源文件中,处理宏定义,条件编译指令等。
- 结果是一个扩展的源代码文件,通常以 `.i` 文件(C语言)或 `.ii` 文件(C++语言)表示。
2. 编译(Compilation):
- 编译器将预处理后的源代码翻译成汇编语言。
- 在这个阶段,编译器进行词法分析、语法分析、语义分析、中间代码生成和优化等操作。
- 生成的汇编语言文件通常以 `.s` 文件表示。
3. 汇编(Assembly):
- 汇编器将汇编语言转换成机器语言指令,这些指令是二进制形式的,可以被计算机的CPU直接执行。
- 生成的目标代码文件通常以 `.o`(Linux/Unix系统)或 `.obj`(Windows系统)表示。
4. 链接(Linking):
- 链接器将一个或多个目标文件以及所需的库文件合并成一个可执行文件。
- 在这个阶段,链接器解析外部引用和符号,合并相同的函数和数据,进行重定位等操作。
- 最终生成的可执行文件通常以 `.exe`(Windows系统)或无扩展名(Linux/Unix系统)表示。
5. 加载(Loading):
- 当程序运行时,操作系统负责将可执行文件加载到内存中。
- 加载器读取可执行文件,并将程序代码和数据映射到内存的适当位置。
6. 执行(Execution):
- CPU开始执行程序的主函数,程序正式开始运行。
- 在执行过程中,程序可能会进行各种计算、输入输出操作、调用库函数等。
7. 终止(Termination):
- 程序执行完成后,或者遇到无法处理的错误时,程序会终止。
- 在终止前,程序可能需要执行一些清理工作,如关闭打开的文件、释放分配的内存等。
链接之前各文件没有交互,模版没有实例化没有生成指令,代码,链接就出现问题。
3.3 解决方法
1. 将声明和定义放到一个文件 "xxx.hpp" 里面或者 xxx.h 其实也是可以的 。 推荐
用的地方就直接有定义了。
2. 模板定义的位置显式实例化 。这种方法不实用,不推荐。
显示实例化:
template
int Add(const int&left,const int&right)
对于其他类型照样,所以比较复杂
4. 模板总结
【优点】
1. 模板复用了代码,节省资源,更快的迭代开发, C++ 的标准模板库 (STL) 因此而产生
2. 增强了代码的灵活性
【缺陷】
1. 模板会导致代码膨胀问题,也会导致编译时间变长
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
结束语
本篇就到此结束啦,内容有点多,相信大家通过本篇博客,对模版的认识有了进一步的理解。
最后感谢各位友友的支持,支持小编的留个赞吧!!!