C++模版进阶

文章目录

  • C++模版进阶
    • 1、非类型模版参数
    • 2、模版的特化
      • 2.1、概念
      • 2.2、函数模版特化
      • 2.3、类模版特化
        • 2.3.1、类模版全特化
        • 2.3.1、类模版偏特化
      • 2.4、类模版特化示例
    • 3、模版的分离编译
      • 3.1、 什么是分离编译
      • 3.2、模版的分离编译
    • 4、模版总结

img

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;
}
  • 注意:
  1. 浮点数、类对象以及字符串是不允许作为非类型模板参数的。
  2. 非类型的模板参数必须在编译期就能确认结果。

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、函数模版特化

  • 函数模板的特化步骤:
  1. 必须要先有一个基础的函数模板
  2. 关键字template后面接一对空的尖括号<>
  3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
  4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
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、模版总结

  • 优点:
  1. 代码重用: 模板允许你编写通用的代码,可以用于不同类型的数据。这样可以避免编写大量类似的代码,提高代码的重用性。

  2. 类型安全: 使用模板可以在编译时进行类型检查,从而提高代码的类型安全性。模板可以确保在不同的场景下使用正确的类型。

  3. 灵活性: 模板允许你编写灵活的代码,因为模板的类型可以在编译时确定,而不是在运行时确定。这使得代码更加灵活,可以适应不同的需求。

  4. 标准库支持: C++ 标准库中大量的容器(如 std::vectorstd::list)、算法(如 std::sortstd::find)等都是使用模板实现的,这为开发人员提供了丰富的工具库。

  • 缺点:
  1. 编译时间: 使用模板可能会增加编译时间,特别是在模板实例化时会生成大量的代码。如果模板被频繁使用或者包含的头文件较多,编译时间可能会显著增加。

  2. 可读性: 模板代码可能会比非模板代码更加复杂,因为模板通常需要使用一些特殊的语法和技巧。这可能会降低代码的可读性,使得代码维护和调试更加困难。

  3. 错误消息: 当使用模板时,编译器生成的错误消息可能会变得更加复杂和晦涩,因为模板涉及到类型推断、模板参数推断等复杂的机制,导致错误消息不易理解。

  4. 代码膨胀: 模板会导致代码膨胀,因为每个模板实例化都会生成一份独立的代码。如果模板被频繁使用,可能会导致可执行文件的大小增加。

  5. 可移植性: 模板的实现在不同的编译器之间可能存在差异,这可能会影响代码的可移植性。某些特定的模板特性可能不被某些编译器支持,或者在不同编译器下的行为不同。


OKOK,C++模版进阶就到这里。如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。

Xpccccc的github主页

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/538796.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

服务注册与发现:Nacos

为什么需要服务注册与发现 假设 mafeng-user 用户微服务部署了多个实例&#xff08;组成集群模式&#xff09;&#xff0c;如下图所示&#xff1a; 会出现以下几个问题&#xff1a; mafeng-order订单微服务发出Http远程调用时&#xff0c;该如何得知mafeng-user实例的IP和端口…

“禁止互撕”新规第二天,热搜把#章子怡“怒怼”网友#推上了榜一

3月12日&#xff0c;微博热搜发布公告&#xff0c;对热搜词条处置规则进行了更新。 针对热搜词条长期以来存在的引战互撕、挑唆对立等不良现象&#xff0c;热搜生态秩序亟待改善&#xff0c;微博给出了两大解决方案&#xff1a; 一是更新热搜词条处置规则&#xff0c;当热搜词…

AJAX 05 axios拦截器、数据管理平台

AJAX 学习 AJAX 05 黑马头条-数据管理平台项目准备业务1&#xff1a;验证码登录bootstrap提示框实际业务中的验证码登录token 【注】HTML遗落的知识【注】JS遗漏的知识业务2&#xff1a;个人信息设置 & axios拦截器axios请求拦截器axios响应拦截器 业务3&#xff1a;发布文…

人工智能课题、模型源码

人工智能研究生毕业&#xff5e;深度学习、计算机视觉、时间序列预测&#xff08;LSTM、GRU、informer系列&#xff09;、python、人工智能项目代做和指导&#xff0c;各种opencv图像处理、图像分类模型&#xff08;vgg、resnet、mobilenet、efficientnet等&#xff09;、人脸检…

回归预测 | Matlab实现GSWOA-KELM混合策略改进的鲸鱼优化算法优化核极限学习机的数据回归预测

回归预测 | Matlab实现GSWOA-KELM混合策略改进的鲸鱼优化算法优化核极限学习机的数据回归预测 目录 回归预测 | Matlab实现GSWOA-KELM混合策略改进的鲸鱼优化算法优化核极限学习机的数据回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 GSWOA-KELM多变量回归预测…

NB-IoT模块

目录 一. NB-IoT模块实物图 二. BC20/NB-IoT模块产品规格 三. 指令顺序 1. AT判断BC20模组是否正常 2. ATE0返回OK&#xff0c;已经返回回显 3. ATCSQ 4. AT_CEREG? 5. ATCGATT? 6. ATCGATT? 四. OneNet 连接 1. AT 查看 NB(当前NB)&#xff0c;云平台根据这两个…

Gitlab CICD 下载artifacts文件并用allure打开,或bat文件打开

allure命令行打开aritfacts报告 首先下载allure.zip&#xff0c;并解压 配置环境变量 使用命令行打开allure文件夹 allure open 2024-03-11-14-54-40 2024-03-11-14-54-40 包含index.html Bat文件打开artifacts There are 2 html reports in the download artifacts.zip S…

HAProxy——高性能负载均衡器

目录 一.常见的Web集群调度器 二.HAProxy基本介绍 1.HAProxy是什么&#xff1f; 2.HAProxy的特性 3.HAProxy常用的8种负载均衡调度算法 3.1 轮询&#xff1a;RR&#xff08;Round Robin&#xff09; 3.2 最小连接数&#xff1a;LC&#xff08;Least Connections&#xff…

卷径计算(膜厚叠加+数值积分器应用博途PLC SCL代码)

VN积分法卷径计算的其它方法,可以参考下面文章链接: 1、VN积分法卷径计算FB https://rxxw-control.blog.csdn.net/article/details/131612206https://rxxw-control.blog.csdn.net/article/details/1316122062、PLC数值积分器 https://rxxw-control.blog.csdn.net/article/…

leetcode代码记录(找到小镇的法官

目录 1. 题目&#xff1a;2. 我的代码&#xff1a;小结&#xff1a; 1. 题目&#xff1a; 小镇里有 n 个人&#xff0c;按从 1 到 n 的顺序编号。传言称&#xff0c;这些人中有一个暗地里是小镇法官。 如果小镇法官真的存在&#xff0c;那么&#xff1a; 小镇法官不会信任任何…

鸿蒙原生应用又又又……扩圈了!你打算何时入局?

继1月18日华为鸿蒙生态千帆启航后不久&#xff0c;先有深圳新政支持鸿蒙原生应用发展&#xff0c;再有多领域抢跑&#xff01;重庆市鸿蒙原生应用浪潮。如今又有一重量级的互联网大厂旗下产品加入。 3月14日下午&#xff0c;**阿里旗下的 11 款应用已正式启动鸿蒙原生应用的开…

LLVM-3.5 —— 01记,编译 LLVM 3.5.0 clang and clang-query

包括编译&#xff1a;clang clang-tools-extra 0, prepare env sudo apt install llvm sudo apt install clang 使用最新的g 会出错。 1, source code $ git clone --recursive $ cd llvm-project $ git checkout llvmorg-3.5.0 $ cp -r ./clang ./llvm/tools/ $ mkdir llv…