C++:模版进阶 | Priority_queue的模拟实现
创作不易,感谢三连支持
一、非类型模版参数
模板参数分类为类型形参与非类型形参。
类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。
非类型形参,就是用一个常量作为类(函数)模板的一个参数,在类(函数)模板中可将该参数当成常量来使用。
注意:
非类型的模板参数必须在编译期就能确认结果。(分离编译会讲解)
我们来介绍一个c++11引入的array

array的底层其实封装的是一个静态数组。并且用到了非类型形参,在这里指代的是底层静态数组的容量大小。
思考:
1、为什么要有这个非模版形参??define定义宏常量难道不香吗??
define定义宏常量有时也可以解决问题,但是宏常量的作用域是全局,比如我们想让一个数组是10的容量,一个数组是20的容量,显然是做不到的,但是模版是可以做到的!!我们不传的时候N就是缺省值,传的时候就是我们指定的容量。

2、我直接用静态数组不行吗?为什么非得用类把他封起来??
(1)为了增加检查的功能,我们知道vs对于静态数组的越界功能是抽查行为,因此不是很安全,而我们把他封装在类里面,这样就可以去写个断言来防止数组越界

(2)[ ]的重载不仅可以增加检查功能,还可以去控制静态数组内容是否可以被写,同时还可以利用这个类去增加许多新的接口

3.能够作为非类型模版参数的有哪些类型??
只能是和int相似类型的才行,比如char、short、int、long int ……浮点数类对象以及字符串是不允许作为非类型模板参数的。
二、模版的特化
通常情况下,使用模板可以实现一些与类型无关的代码,但对于一些特殊类型的可能会得到一些错误的结果,需要特殊处理,比如:实现了一个专门用来进行小于比较的函数模板
可以看到,Less绝对多数情况下都可以正常比较,但是在特殊场景下就得到错误的结果。上述示例中,p1指向的d1显然大于p2指向的d2对象,但是Less内部并没有比较p1和p2指向的对象内容,而比较的是p1和p2指针的地址,这就无法达到预期而错误。
此时,就需要对模板进行特化。即:在原模板类的基础上,针对特殊类型所进行特殊化的实现方式。模板特化中分为函数模板特化与类模板特化。
2.1 函数模版特化
函数模板的特化步骤:
1. 必须要先有一个基础的函数模板
2. 关键字template后面接一对空的尖括号<>
3. 函数名后跟一对尖括号,尖括号中指定需要特化的类型
4. 函数形参表: 必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
我们展示一下用法:

相当于是我们特殊化了一个版本出来,这个版本可以去比较指针解引用的内容!
但是我们还有这样一个方法——利用函数匹配的规则,直接把这个特殊类型的函数给出来

我们可以把函数模版当成是冰箱里的菜,模版特化的函数当成是预制菜,最后这个简单函数是现成菜。当我们有现成的吃的时候,就不会考虑去吃没做过的菜。这其实就是函数匹配规则!
并且这种函数实现简单明了,代码的可读性高,容易书写,因为对于一些参数类型复杂的函数模板,特化时特别给出,因此函数模板不建议特化。
2.2 类模版特化
函数有匹配规则,所以其实不怎么依赖特化,但是类并没有匹配规则啊!!所以特化最广泛的使用是在类中。类模版特化的步骤和函数模版特化的步骤是相似的。
2.2.1 全特化
全特化即是将模板参数列表中所有的参数都确定化
2.2.2 部分特化
部分特化的意思就是说,我们把其中一部分参数进行特化了。

2.2.3 偏特化(非常重要)
偏特化并不仅仅是指特化部分参数,而是针对模板参数更进一步的条件限制所设计出来的一个特化版本。
比如偏特化为指针类型(比较常见),或者是偏特化为引用类型(不常见)。

后面讲到仿函数的时候会做更进一步的介绍
三、模版分离编译
一个程序(项目)由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件链接起来形成单一的可执行文件的过程称为分离编译模式。
一般来说,在我们书写大项工程的时候,为了保证代码的简洁性,我们常常将函数声明放在一个头文件里,将函数定义放在一个源文件里,然后再用另外一个源文件去进行测试。但是在模版这个地方,分离编译成为了一个难题!
3.1 模版的分离编译
假如有以下场景,模板的声明与定义分离开,在头文件中进行声明,源文件中完成定义:
// flby.h
template<class T>
T Add(const T& left, const T& right);
// flby.cpp
#include"flby.h"
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
// test.cpp
#include"flby.h"
int main()
{
Add(1, 2);
Add(1.0, 2.0);
return 0;
}

为什么会这样呢??


3.2 解决方法
方法一:将声明和定义放到一个文件 "xxx.hpp" 里面或者xxx.h其实也是可以的。
因为最后都会在测试文件里面展开,这样编译的过程就可以进行实例化生成函数。一般比较推荐使用这种。
方法二:模板定义的位置显式实例化。这种方法不实用,不推荐使用。
显式实例化的意思就是,你不是推断不出来吗??那我就直接告诉你要生成什么样的函数!
四、模版的总结
优点:
1. 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生
2. 增强了代码的灵活性
缺陷:
1. 模板会导致代码膨胀问题,也会导致编译时间变长(需要推导并生成实例化函数)
2. 出现模板编译错误时,错误信息非常凌乱,不易定位错误
五、priority_queue的介绍
priority_queue的文档介绍
1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大(小)的。
2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。
3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
empty():检测容器是否为空
size():返回容器中有效元素个数
front():返回容器中第一个元素的引用
push_back():在容器尾部插入元素
pop_back():删除容器尾部元素
5. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。

其实优先级队列就是我们数据结构里的堆!!
DS:二叉树的顺序结构及堆的实现_顺序打印堆-CSDN博客
大家可以看看博主的这篇博客,堆主要的应用就是解决top-k问题,在这篇文章里有具体的分析。
六、priority_queue的模拟实现
//仿函数
template<class T>
struct less //冰箱里的菜
{bool operator()(const T& x, const T& y) const{return x < y;}
};
template<class T>
struct greater
{bool operator()(const T& x, const T& y) const{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;while (child > 0){if (Compare()(_con[parent], _con[child]))//_con[child] > _con[parent]{std::swap(_con[child], _con[parent]);child = parent;parent = (child - 1) / 2;}elsebreak;}}void adjust_down(int parent){int child = parent * 2 + 1;//建大堆,找大,假设左孩子比右孩子大while (child < _con.size()){if (child + 1 < _con.size() && (Compare()(_con[child], _con[child + 1])))//_con[child] < _con[child + 1]++child;if (Compare()(_con[parent], _con[child]))//_con[parent] < _con[child]{std::swap(_con[parent],_con[child]);parent = child;child = parent * 2 + 1;}elsebreak;}}bool empty() const{return _con.empty();}size_t size() const{return _con.size();}const T& top() const{return _con[0];}void push(const T& val){_con.push_back(val);//插入后要向上调整adjust_up(_con.size() - 1);//向上调整算法}void pop(){std::swap(_con[0], _con[_con.size() - 1]);_con.pop_back();adjust_down(0);//向下调整算法}void swap(priority_queue<T>& q){_con.swap(q._con);}private:Container _con;};
这边我们用到了两个仿函数,如果是建大堆的话,用less<T>,如果是建小堆的话,用greater<T>,仿函数就是通过重载()的行为,模拟出函数的效果,比函数指针会易用很多
七、模版特化的深入分析
假设我们放了一个日期类进去,能进行比较吗??

答案是可以的,前提是我们需要在日期类重载出比较操作符
如果是以下这种情况呢??
虽然指针也可以比较大小,但是指针比较大小的方式显然不符合我们的预期,我们希望的是比较指针解引用的内容,这个时候应该怎么办呢?
第一个方法:再写一个专门用来比较指针解引用的仿函数
struct PDateless{bool operator()(const Date* p1, const Date* p2){return *p1 < *p2;}};struct PDateGreater{bool operator()(const Date* p1, const Date* p2){return *p1 > *p2;}};

第二个方法:对原来的less和greater进行全特化出一个专门比较Date指针类型的仿函数
//全特化版本(因为全特化了,只能针对Data*) (即食菜)template<>struct less<Date*>{bool operator()(const Date* x, const Date* y) const{return *x < *y;}};
//全特化版本(因为全特化了,只能针对Data*)
template<>
struct greater<Date*>
{bool operator()(const Date* x, const Date* y) const{return *x > *y;}
};
但是以上两种方法是不是都不太好,因为无论是全特化还是写一个全新的仿函数,我们都只是针对了Date*类型,但是如果我们以后遇到int* double*……难道也要再写一个吗??所以就有了一个更好的方法。
方法三:对原来的less和greater进行偏特化限制出一个专门比较任意指针类型的仿函数
//偏特化版本 具体类型,针对指针这个泛类 必须在原来的基础之上 (预制菜)
template<class T>
struct less<T*>
{bool operator()(const T* x, const T* y) const{return *x < *y;}
};
//偏特化版本 (针对所有类型的指针)
template<class T>
struct greater<T*>
{bool operator()(const T* x, const T* y) const{return *x > *y;}
};
我们这个时候发现,偏特化可以帮助我们更好地解决指针比较这样的问题。
给大家举个形象的比喻,模版就相当于是冰箱里的菜,全特化版本就相当于是即食菜,而偏特化就相当于是预制菜。重新写一个特定的仿函数就相当于是外卖
外卖>即食菜>预制菜>冰箱里的菜。
1、 即食菜和预制菜理论上来说都是通过冰箱里的菜走出来的,所以必须要先有模版才能进行全特化和偏特化。
2、从前往后优先级变低。

相关文章:
C++:模版进阶 | Priority_queue的模拟实现
创作不易,感谢三连支持 一、非类型模版参数 模板参数分类为类型形参与非类型形参。 类型形参即:出现在模板参数列表中,跟在class或者typename之类的参数类型名称。 非类型形参,就是用一个常量作为类(函数)模板的一个参数&…...
【刷题记录】详谈设计循环队列
下题目为个人的刷题记录,在本节博客中我将详细谈论设计循环队列的思路,并给出代码,有需要借鉴即可。 题目:LINK 循环队列是线性表吗?或者说循环队列是线性结构吗? 对于这个问题,我们来看一下线…...
人工智能|机器学习——k-近邻算法(KNN分类算法)
1.简介 k-最近邻算法,也称为 kNN 或 k-NN,是一种非参数、有监督的学习分类器,它使用邻近度对单个数据点的分组进行分类或预测。虽然它可以用于回归问题,但它通常用作分类算法,假设可以在彼此附近找到相似点。 对于分类…...
乐得瑞 1C to 2C快充线:引领充电数据线新潮流,高效快充解决接口难题
随着科技的不断进步,数据线的接口种类也日渐繁多,但在早些时候,三合一和二合一的数据线因其独特的设计而备受欢迎。这类数据线通常采用USB-A口作为输入端,并集成了Micro USB、Lightning以及USB-C三种接口,满足了当时市…...
O2OA(翱途)开发平台如何在流程表单中使用基于Vue的ElementUI组件?
本文主要介绍如何在O2OA中进行审批流程表单或者工作流表单设计,O2OA主要采用拖拽可视化开发的方式完成流程表单的设计和配置,不需要过多的代码编写,业务人员可以直接进行修改操作。 在流程表单设计界面,可以在左边的工具栏找到Ele…...
0 OpenHarmony开源鸿蒙NEXT星河版内核嵌入式编程
开源鸿蒙NEXT星河版内核嵌入式编程 作者将狼才鲸创建日期2024-03-08 CSDN文章阅读地址Gitee文章下载地址 一、前景提要 2024年1月18日,华为放出HarmonyOS NEXT 鸿蒙星河版开发者预览版本(不是HarmonyOS NEXT版,是HarmonyOS NEXT星河版&…...
Vue | 基于 vue-admin-template 项目的跨域问题解决方法
目录 一、现存问题 二、解决方法 2.1 修改的第一个地方 2.2 修改的第二个地方 2.3 修改的第三个地方 自存 一、现存问题 报错截图如下: 二、解决方法 2.1 修改的第一个地方 在 .env.development 文件中: # base api # VUE_APP_BASE_API /d…...
mutex 和 channel 哪一个工作效率更高?
关于Rust中mutex和channel哪一个工作效率更高的问题,实际上并没有一个绝对的答案,因为效率的高低取决于具体的使用场景和需求。 互斥锁(mutex)主要用于保护共享资源,确保一次只有一个线程可以访问它。当需要多个线程同…...
Elasticsearch 通过索引阻塞实现数据保护深入解析
Elasticsearch 是一种强大的搜索和分析引擎,被广泛用于各种应用中,以其强大的全文搜索能力而著称。 不过,在日常管理 Elasticsearch 时,我们经常需要对索引进行保护,以防止数据被意外修改或删除,特别是在进…...
备考银行科技岗刷题笔记(持续更新版)
银行考试计算机部分复习 IEEE 802.11的帧格式 1.1 IEEE 802.11是什么? 802.11是国际电工电子工程学会(IEEE)为无线局域网络制定的标准。目前在802.11的基础上开发出了802.11a、802.11b、802.11g、802.11n、802.11ac。并且为了保证802.11更…...
代码随想录算法训练营第五十五天|583. 两个字符串的删除操作、72. 编辑距离。
583. 两个字符串的删除操作 题目链接:两个字符串的删除操作 题目描述: 给定两个单词 word1 和 word2 ,返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符串中的一个字符。 解题思路: 1、确定dp数组&#x…...
Softmax 回归 + 损失函数 + 图片分类数据集【动手学深度学习v2】李沐动手学深度学习课程笔记
目录 Softmax回归 损失函数 图片分类数据集 Softmax回归从零开始实现 Softmax回归简洁实现 Softmax回归 回归和分类的区别 回归问题举例上节课的预测房价问题,分类问题就是对样本进行分类 回归和分类的具体区别 假设真实的类别为第i个类别(值为1&#x…...
git 初始化项目并上传到github
如果还没配置过,需要配置账号信息 git config --global user.name "baymax-collab" git config --global user.email "baymax-collabtest.com"创建一个新的存储库 git clone gitgithub.com:xxxx cd test git switch --create main touch READ…...
前端javascript的DOM对象操作技巧,全场景解析
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 所属的专栏:前端泛海 景天的主页:景天科技苑 文章目录 1.js的DOM介绍2.节点元素层级关系3.通过js修改,清空节点…...
TCP包头、TCP为什么安全可靠、UDP和TCP的区别、http协议
我要成为嵌入式高手之3月8日Linux高编第十八天!! __________________________________________________ 学习笔记 TPC包头 1、序号 发送端发送数据包的编号 2、确认号 已经确认接收到的数据的编号,只有当ACK为1时,该位才有用 …...
Android使用WebView打开内嵌H5网页
Android打开外部网页链接请参考上一篇文章 https://public.blog.csdn.net/article/details/136384559 继上篇,新建assets文章夹,将H5的网页资源放到此文件夹下 把H5的资源文件都拷进来 这个时候,将添加打开本地网页的代码: //打…...
UDP实现文件的发送、UDP实现全双工的聊天、TCP通信协议
我要成为嵌入式高手之3月7日Linux高编第十七天!! ———————————————————————————— 回顾 重要程序 1、UDP实现文件的发送 发端: #include "head.h"int main(void) {int sockfd 0;struct sockaddr_i…...
Yocto - Project Quick Build
欢迎光临! 这篇简短的文档将向您介绍使用 Yocto 项目构建典型镜像的过程。本文还介绍了如何为特定硬件配置构建。您将使用 Yocto Project 构建一个名为 Poky 的参考嵌入式操作系统。 Welcome! This short document steps you through the process for a typical i…...
深入探讨C++中的可变参数列表(Variadic Templates)
文章目录 导言可变参数列表的基本用法使用std::initializer_list应用场景 导言 在C编程中,处理可变数量参数的能力是一种非常有用的功能。通过可变参数列表,你可以编写更加通用和灵活的函数,从而提高代码的可读性和重用性。本文将详细介绍C中…...
MS2548 国产自动方向控制、半双工 RS-485 收发器 替代MAX13487
MS2548 国产自动方向控制、半双工 RS-485 收发器 替代MAX13487 北京冠宇铭通科技有限公司 肖小姐 产品简述 MS2548 是一个 5V 供电、半双工 RS-485 收发器。 芯片具有自动换向控制功能,可用于隔离485 端口,驱动器输入与使能信号一起配合控制芯片的状态&…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
系统设计 --- MongoDB亿级数据查询优化策略
系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log,共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题,不能使用ELK只能使用…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
JDK 17 新特性
#JDK 17 新特性 /**************** 文本块 *****************/ python/scala中早就支持,不稀奇 String json “”" { “name”: “Java”, “version”: 17 } “”"; /**************** Switch 语句 -> 表达式 *****************/ 挺好的ÿ…...
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...

