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.rhkb.cn/news/277122.html

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

相关文章

IO Watch:用 Arduino UNO 制造的可编程手表

MAKER:mblaz/译:趣无尽 Cherry(转载请注明出处) 关于手表的项目,之前我们已经介绍过一款《Arduino + 3D 打印 DIY 电子手表》。本期的项目同样的一款基于 Arduino UNO 的可编程的手表,相比之下制造门槛更高一些。同时它更成熟、实用,外形也很有设计感,非常的漂亮! 这…

TCP相关特性

协议段格式 • 源/⽬的端⼝号:表⽰数据是从哪个进程来,到哪个进程去; • 32位序号/32位确认号:后⾯详细讲; • 4位TCP报头⻓度:表⽰该TCP头部有多少个32位bit(有多少个4字节);所以TCP头部最⼤⻓度是15*460 • 6位标志位: ◦ URG:紧急指针是否有效 ◦ ACK:确认号是否有效…

如何使用ROS和easymqos快速搭建一辆语音控制导航的机器人

之前做的机器人小车基本都属于电脑或手机控制操作。目前&#xff0c;使用语音控制机器人小车运动&#xff0c;让机器人导航去指定地点&#xff0c;已经成为热门&#xff0c;并且语音识别技术已经有落地方案&#xff0c;可满足生活中的基本需要。有些语音芯片通过高算力处理器运…

【HBase入门与实战】一文搞懂HBase!

HBase入门与实战 目录 HBase入门与实战内容要点一、引入HBase二、了解NoSQL的概念三、NoSQL、BI、大数据的关系四、HBase概述五、HBase应用场景 内容要点 HBase的引入、定义和特点NoSQL数据库的概念和与关系型数据库的区别HBase的物理架构和逻辑架构HBase Shell的基本命令使用…

PHP异世界云商系统开源源码

系统更新与修复列表 1. 基于彩虹的二次开发 - 对彩虹系统进行了二次开发&#xff0c;增强了系统的功能和性能。2. 新增自定义输入框提示内容&#xff08;支持批量修改&#xff09; - 用户可以自定义输入框的提示内容&#xff0c;并支持批量修改&#xff0c;提升用户体验。3. 新…

劲仔食品三年倍增,抢先打响鹌鹑蛋“健康”属性品牌之争?

如果说&#xff0c;进入2024年后&#xff0c;在股价继续陷入回调状态的食品板块中有个股走势表现相对亮眼&#xff0c;那么劲仔食品必是其中之一。 从去年发布2023年三季度业绩公告以来&#xff0c;其强劲的业绩表现就带动了股价走出小趋势。2023年10月23日至今2024年3月13日收…

C#构建类库

类库程序集能将类型组合成易于部署的单元&#xff08;DLL文件&#xff09;&#xff0c;为了使编写的代码能够跨多个项目重用&#xff0c;应该将他们放在类库程序集中。 一、创建类库 在C#中&#xff0c;构建类库是指创建一个包含多个类的项目&#xff0c;这些类可以被其他应用…

RocketMQ 面试题及答案整理,最新面试题

RocketMQ的消息存储机制是如何设计的&#xff1f; RocketMQ消息存储机制的设计原理&#xff1a; 1、CommitLog文件&#xff1a; 所有的消息都存储在一个连续的CommitLog文件中&#xff0c;保证了消息的顺序写入&#xff0c;提高写入性能。 2、消费队列&#xff1a; 为每个主…

Task-balanced distillation for object detection用于

Task-balanced distillation for object detection用于目标检测的任务平衡蒸馏 摘要 主流的目标检测器通常由分类和回归两个子任务组成&#xff0c;由两个并行头部实现。这种经典的设计范式不可避免的导致分类得分和定位质量&#xff08;IOU&#xff09;之间的空间分布不一致…

漫途桥梁结构安全监测方案,护航桥梁安全!

桥梁作为城市生命线的重要组成部分&#xff0c;承载着城市交通、物流输送、应急救援等重要职能。然而&#xff0c;随着我国社会经济的飞速发展&#xff0c;桥梁所承载的交通流量逐年增长&#xff0c;其安全性所面临的挑战亦日益严峻。例如恶劣的外部环境、沉重的荷载以及长期使…

python爬虫实战——抖音

目录 1、分析主页作品列表标签结构 2、进入作品页前 判断作品是视频作品还是图文作品 3、进入视频作品页面&#xff0c;获取视频 4、进入图文作品页面&#xff0c;获取图片 5、完整参考代码 6、获取全部作品的一种方法 本文主要使用 selenium.webdriver&#xff08;Firef…

HarmonyOS NEXT应用开发—自定义视图实现Tab效果

介绍 本示例介绍使用Text、List等组件&#xff0c;添加点击事件onclick,动画&#xff0c;animationTo实现自定义Tab效果。 效果预览图 使用说明 点击页签进行切换&#xff0c;选中态页签字体放大加粗&#xff0c;颜色由灰变黑&#xff0c;起到强调作用&#xff0c;同时&…

揭示数据在内存中存储的秘密!

** ** 悟已往之不谏&#xff0c;知来者犹可追 ** ** 创作不易&#xff0c;宝子们&#xff01;如果这篇文章对你们有帮助的话&#xff0c;别忘了给个免费的赞哟~ 整数在内存中的存储 整数的表达方式有三种&#xff1a;原码、反码、补码。 三种表示方法均有符号位和数值位两部分…

Oracle数据库:使用 bash脚本 + 定时任务 自动备份数据

Oracle数据库&#xff1a;使用 bash脚本 定时任务 自动备份数据 1、前言2、为什么需要自动化备份&#xff1f;3、编写备份脚本4、备份脚本授权5、添加定时任务6、重启 crond / 检查 crond 服务状态7、备份文件检查 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收…

torch.backends.cudnn.benchmark 作用

相关参数 torch.backends.cudnn.enabled torch.backends.cudnn.benchmark torch.backends.cudnn.deterministictorch.backends.cudnn.benchmark True&#xff1a;将会让程序在开始时花费一点额外时间&#xff0c;为整个网络的每个卷积层搜索最适合它的卷积实现算法&#xff0c…

力扣59. 螺旋矩阵 II

思路&#xff1a;此题思路就是绕圈遍历&#xff0c;全靠条件处理技巧&#xff0c;重点要清楚的就是循环不变量&#xff1a;左闭右开&#xff08;即拐弯处的一个数&#xff0c;留给第二行处理&#xff09; 以下是代码随想录的作者的一张图片&#xff0c;每次for循环&#xff0c;…

Docker容器化技术(使用Docker搭建论坛)

第一步&#xff1a;删除容器镜像文件 [rootlocalhost ~]# docker rm -f docker ps -aq b09ee6438986 e0fe8ebf3ba1第二步&#xff1a;使用docker拉取数据库 [rootlocalhost ~]# docker run -d --name db mysql:5.7 02a4e5bfffdc81cb6403985fe4cd6acb0c5fab0b19edf9f5b8274783…

深入浅出计算机网络 day.2 概论⑥ 计算机网络体系结构

上帝疯狂杜撰世界悲情的命题 将凉薄和荒芜尽写 —— 24.3.13 内容概述 1.常见的三种计算机网络体系结构 2.计算机网路体系结构分层的必要性 3.计算机网络体系结构分层思想举例 4.计算机网络体系结构中的专用术语 一、常见的三种计算机网络体系结构 1.OSI参考模型 …

基于ElasticSearch存储海量AIS数据:时空立方体索引篇

文章目录 引言I 时间维切分II 空间范围切分引言 索引结构制约着查询请求的类型和处理方式,索引整体架构制约着查询请求的处理效率。随着时间推移,AIS数据在空间分布上具备局部聚集性,如 果简单地将所有AIS数据插入一个索引结构,随着数据量增长,索引的更新效率、查询效率及…

【Linux】Centos7上安装MySQL5.7

目录 1.下载安装包2. 上传安装包3.将 mysql 解压到/usr/local/4.重命名5.创建mysql用户及用户组6. 进入 mysql 目录修改权限7. 安装依赖库8. 执行安装脚本9. 复制启动脚本到资源目录10. 拷贝 my.cnf&#xff0c;并赋予权限11. 配置环境变量12. 启动 mysqld13. 登录 MySQL&#…