文章目录
- C++模版进阶
- 1、非类型模版参数
- 2、模版的特化
- 2.1、概念
- 2.2、函数模版特化
- 2.3、类模版特化
- 2.3.1、类模版全特化
- 2.3.1、类模版偏特化
- 2.4、类模版特化示例
- 3、模版的分离编译
- 3.1、 什么是分离编译
- 3.2、模版的分离编译
- 4、模版总结
C++模版进阶
1、非类型模版参数
模板参数分为类型形参与非类型形参
类型形参:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参:就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用
#include <iostream>using namespace std;namespace xp {template<class T, size_t N = 10>class array {public:T &operator[](size_t pos) {return _array[pos];}bool empty() {return 0 == _size;}private:T _array[N];size_t _size;}; }int main() {xp::array<int> arr;cout << arr[0] << endl; // 随机值cout << arr.empty() << endl;return 0; }
- 注意:
- 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
- 非类型的模板参数必须在编译期就能确认结果。
2、模版的特化
2.1、概念
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板
template<class T> bool Less(const T x, const T y) {return x < y; }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; }
可以看到,Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中,p1指向的d1显然小于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2指针的地址,这就无法达到预期而错误。此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。
2.2、函数模版特化
- 函数模板的特化步骤:
- 必须要先有一个基础的函数模板
- 关键字template后面接一对空的尖括号<>
- 函数名后跟一对尖括号,尖括号中指定需要特化的类型
- 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
template<class T> bool Less(const T x, const T y) {return x < y; }// 函数模版特化 template<> bool Less<Date*>(const Date* x, const Date* y) {return *x < *y; }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; }
虽然解决了特殊情况特殊处理,但是感觉还是很鸡肋,因为我如果想比较两个Date*的数据,可以重载这个函数,如下:
bool Less(const Date* x, const Date* y) {return *x < *y; }
这种代码更清晰明了。因此不建议使用函数模版。
- 注意:函数模版不支持偏特化,默认就是全特化。
2.3、类模版特化
2.3.1、类模版全特化
- 全特化即是将模板参数列表中所有的参数都确定化
template<class T1, class T2> class Date1 { public:Date1() {cout << "Date <T1 ,T2>" << endl;}private:T1 _d1;T2 _d2; };// 类模版全特化 template<> class Date1<char, int> { public:Date1() {cout << "Date <char ,int>" << endl;}private:int _d1;char _d2; };int main() {Date1<char, char> d1; // Date <T1 ,T2>Date1<char, int> d2; // Date <char ,int>return 0; }
2.3.1、类模版偏特化
- 偏特化即是任何针对模版参数进一步进行条件限制设计的特化版本。比如对于以下模板类:
template<class T1, class T2> class Date1 { public:Date1() {cout << "Date <T1 ,T2>" << endl;}private:T1 _d1;T2 _d2; };
仅部分特化类型
template<class T> class Date1 <T,int>{ public:Date1() {cout << "Date <T,int>" << endl;}private:T _d1;int _d2; };
限制模版参数类型
//两个参数偏特化为指针类型 template<class T> class Date1 <T*,T*>{ public:Date1() {cout << "Date <T*,T*>" << endl;}private:T* _d1;T* _d2; };//两个参数偏特化分别为引用类型和指针类型 template<class T1, class T2> class Date1 <T1&,T2*>{ public:Date1() {cout << "Date <T&,T*>" << endl;}private:T1 _d1;T2 _d2; };int main() {Date1<char, double> d0; // Date <T1 ,T2>Date1<double, int> d1; // Date <T,int>Date1<int *, int *> d2; // Date <T*,T*>Date1<int &, int *> d3; // Date <T&,T*>return 0; }
2.4、类模版特化示例
priority_queue.h
文件#include <iostream> #include <vector> #include <list> #include <deque> #include <algorithm>using namespace std;class Date { public:// 获取某年某月的天数int GetMonthDay(int year, int month) {static int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30,31};int day = days[month];if (month == 2&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) {day += 1;}return day;}// 全缺省的构造函数 //声明和定义分离,需要指定类域Date(int year, int month, int day) {if (year >= 0 && (month >= 1 && month <= 12) && (day <= GetMonthDay(year, month))) {_year = year;_month = month;_day = day;} else {cout << "初始化的日期有误" << endl;assert(year >= 0 && (month >= 1 && month <= 12) && (day <= GetMonthDay(year, month)));}}// 拷贝构造函数 // d2(d1)Date(const Date &d) {_year = d._year;_month = d._month;_day = d._day;}// 赋值运算符重载 // d2 = d3 -> d2.operator=(&d2, d3)Date &operator=(const Date &d) {if (this != &d) {_year = d._year;_month = d._month;_day = d._day;}return *this;}// 析构函数~Date() { // cout << "~Date()" << endl;}// 日期+=天数 -- 改变原值Date &operator+=(int day) {//如果输入的day小于0if (day < 0) {*this -= -day;return *this;}_day += day;//加后的天数大于当月天数的最大值while (_day > GetMonthDay(_year, _month)) {_day -= GetMonthDay(_year, _month);_month++;if (_month == 13) {_month = 1;_year++;}}return *this;}// 日期+天数 -- 不改变原值Date operator+(int day) {Date temp(*this);temp += day;return temp;}// 日期-=天数 -- 改变原值Date &operator-=(int day) {//如果输入的day小于0if (day < 0) {*this += -day;return *this;}_day -= day;while (_day <= 0) {_month--;if (_month == 0) {_year--;if (_year == 0) {printf("错误\n");exit(-1);}_month = 12;}_day += GetMonthDay(_year, _month);}return *this;}// 日期-天数 -- 不改变原值Date operator-(int day) {Date temp(*this);temp -= day;return temp;}// 前置++ -- 先+1再计算Date &operator++() {*this += 1;return *this;}// 后置++ -- 先计算再+1Date operator++(int) {Date temp(*this);//拷贝构造temp += 1;return temp;}// 前置-- -- 先-1再计算Date &operator--() {*this -= 1;return *this;}// 后置-- -- 先计算再-1Date operator--(int) {Date temp(*this);//拷贝构造temp -= 1;return temp;}// >运算符重载bool operator>(const Date &d) const {if (_year >= d._year) {if (_year > d._year)return true;else {//_year == d._yearif (_month >= d._month) {if (_month > d._month)return true;else {//_month == d._monthif (_day >= d._day) {if (_day > d._day)return true;elsereturn false;}}}}}return false;}// ==运算符重载bool operator==(const Date &d) const {return _year == d._year && _month == d._month && _day == d._day;}// >=运算符重载bool operator>=(const Date &d) const {return (*this > d) || (*this == d);}// <运算符重载bool operator<(const Date &d) const {return !(*this >= d);}// <=运算符重载bool operator<=(const Date &d) const {return (*this < d) || (*this == d);}// !=运算符重载bool operator!=(const Date &d) const {return !(*this == d);}// 日期-日期 返回天数int operator-(const Date &d) const {//假设第一个参数的日期更大int flag = 1;int count = 0;Date max = *this;Date min = d;if (*this < d) {flag = -1;max = d;min = *this;}while (max != min) {++min;count++;}return count * flag;}friend ostream &operator<<(ostream &out, Date &d);private:int _year;int _month;int _day; };ostream &operator<<(ostream &out, Date &d) {out << d._year << " " << d._month << " " << d._day << endl;return out; }namespace xp {// 仿函数template<class T>class less {public:bool operator()(const T x, const T y) {return x > y;}};// 类模版偏特化template<class T1>class less<T1 *> {public:bool operator()(const T1 *const &x, const T1 *y) {return *x > *y;}};template<class T>class greater {public:bool operator()(const T x, const T y) {return x < y;}};// 类模版偏特化template<class T2>class greater<T2 *> {public:bool operator()(const T2 *x, const T2 *y) {return *x < *y;}};template<class T, class Container = vector<T>, class Compare= less<T> >class priority_queue {// 底层是一个堆public:// 假设默认是大根堆// 向上调整void Adjust_up(int child) {int parent = (child - 1) / 2;Compare com;while (child > 0) { // if (_con[child] > _con[parent]) {if (com(_con[child], _con[parent])) {swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;} else {break;// 调整结束}}}// 向下调整void Adjust_down(int parent) {Compare com;// 假设左孩子比右孩子更大int child = parent * 2 + 1;//如果右孩子存在if (child + 1 < _con.size() && com(_con[child + 1], _con[child])) {++child;}// 此时child是左右孩子值更大的那个while (child < _con.size()) {if (com(_con[child], _con[parent])) {swap(_con[child], _con[parent]);parent = child;child = parent * 2 + 1;} else {break;// 调整结束}}}void push(const T &val) {_con.push_back(val);Adjust_up(_con.size() - 1);}void pop() {// 第一个元素和最后一个元素交换swap(_con[0], _con[_con.size() - 1]);_con.pop_back();Adjust_down(0);}const T &top() {return _con[0];}size_t size() const {return _con.size();}bool empty() {return _con.empty();}private:Container _con;}; }
main.cpp
文件#include <iostream> #include "priority_queue.h"using namespace std;int main() {Date *d1 = new Date(2024, 3, 11);Date *d2 = new Date(2024, 3, 13);Date *d3 = new Date(2024, 3, 10);// xp::priority_queue<Date *> pq1;xp::priority_queue<Date *, vector<Date *>, xp::greater<Date *>> pq1;// 注意这里greater要指定命名空间pq1.push(d1);pq1.push(d2);pq1.push(d3);while (!pq1.empty()) {cout << *pq1.top();pq1.pop();}cout << "-------------\n";xp::priority_queue<int *> pq2;pq2.push(new int(1));pq2.push(new int(3));pq2.push(new int(2));pq2.push(new int(4));while (!pq2.empty()) {cout << *pq2.top() << endl;pq2.pop();}return 0; }
这里的优先队列里面就实现了就算是指针数据,也可以排序的功能。
3、模版的分离编译
3.1、 什么是分离编译
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。简单来说就是将函数或者类的声明和定义分离,比如声明放在
.h
文件,定义放在.cpp
文件中。
3.2、模版的分离编译
看下面模版的声明和定义分离的情况
template.h
文件template<class T> T Add(const T &x, const T &y);
template.cpp
文件#include "template.h"template<class T> T Add(const T &x, const T &y) {return x + y; }
main.c
文件#include <iostream> #include "template.h"using namespace std;int main() {cout << Add(1,2) << endl; // 出现链接错误return 0; }
问题出在:调用的地方知道怎么实例化但只有声明,定义的地方有模版但不知道怎么实例化 。
cpp
文件不会去扫描所有文件去确定实例化的内容。解决办法:对
cpp
文件模版显式实例化(鸡肋),或者对模版不进行分离编译,声明定义直接放在.h文件或者.hpp文件中。#include "template.h"template<class T> T Add(const T &x, const T &y) {return x + y; }// 显式实例化 template int Add(const int &x, const int &y);
4、模版总结
- 优点:
代码重用: 模板允许你编写通用的代码,可以用于不同类型的数据。这样可以避免编写大量类似的代码,提高代码的重用性。
类型安全: 使用模板可以在编译时进行类型检查,从而提高代码的类型安全性。模板可以确保在不同的场景下使用正确的类型。
灵活性: 模板允许你编写灵活的代码,因为模板的类型可以在编译时确定,而不是在运行时确定。这使得代码更加灵活,可以适应不同的需求。
标准库支持: C++ 标准库中大量的容器(如
std::vector
、std::list
)、算法(如std::sort
、std::find
)等都是使用模板实现的,这为开发人员提供了丰富的工具库。
- 缺点:
编译时间: 使用模板可能会增加编译时间,特别是在模板实例化时会生成大量的代码。如果模板被频繁使用或者包含的头文件较多,编译时间可能会显著增加。
可读性: 模板代码可能会比非模板代码更加复杂,因为模板通常需要使用一些特殊的语法和技巧。这可能会降低代码的可读性,使得代码维护和调试更加困难。
错误消息: 当使用模板时,编译器生成的错误消息可能会变得更加复杂和晦涩,因为模板涉及到类型推断、模板参数推断等复杂的机制,导致错误消息不易理解。
代码膨胀: 模板会导致代码膨胀,因为每个模板实例化都会生成一份独立的代码。如果模板被频繁使用,可能会导致可执行文件的大小增加。
可移植性: 模板的实现在不同的编译器之间可能存在差异,这可能会影响代码的可移植性。某些特定的模板特性可能不被某些编译器支持,或者在不同编译器下的行为不同。
OKOK,C++模版进阶就到这里。如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。
Xpccccc的github主页