C++笔记---可变参数模板

1. 简单介绍与基本语法

可变参数模板是指模板的类型参数列表的的参数个数可变

C++11支持可变参数模板,也就是说支持可变数量参数的函数模板和类模板,可变数目的参数被称为参数包,存在两种参数包:

模板参数包:表示零或多个模板参数。

函数参数包:表示零或多个函数参数。

参数包的加入使得我们可以更加方便地设计出参数个数可变的函数,就比如模拟实现printf。

但C语言由于不支持模板,所以实现printf的方式十分复杂,查了我也没看明白。

声明该类型模板的语法

template<class ...Args> 
Type Func(Args... args) 
{}template<class ...Args> 
Type Func(Args&... args) // 每个参数都按左值引用方式接收
{}template<class ...Args> 
Type Func(Args&&... args) // 每个参数都按万能引用方式接收
{}

省略号用来指出一个由模板参数或函数参数构成的一个包:

在模板参数列表中,class...或typename...指出接下来的参数表示零或多个类型列表。

在函数参数列表中,类型名后面跟...指出接下来表示零或多个形参对象列表。

函数参数包可以用左值引用或右值引用表示,跟前面普通模板一样,每个参数实例化时遵循引用折叠规则。

可变参数模板的原理跟模板类似,本质还是去实例化对应类型和个数的多个函数。

"sizeof..."运算符

这里我们引入一个新的运算符 "sizeof..." ,用于计算参数包中参数的个数:

template <class ...Args>
void Print(Args&&... args)
{cout << sizeof...(args) << endl;
}int main()
{double x = 2.2;Print(); // 包⾥有0个参数Print(1); // 包⾥有1个参数Print(1, string("xxxxx")); // 包⾥有2个参数Print(1.1, string("xxxxx"), x); // 包⾥有3个参数return 0;
}

2. 包扩展(参数包展开)

参数包并不能如我们所想地去直接访问其内容,我们只能采取如下两种方式来访问其包含的参数:

递归展开

虽然我们不能直接取得参数包中的参数,但是在用参数包进行传参时,参数包中的参数会自动匹配形参。

由此为启发,我们可以设计一个参数列表为一个参数和一个参数包的递归模板函数:

Type Func()
{// ...
}template<class T, ...Args>
Type Func(T& x, Args... args)
{// ...Func(args...);
}

函数体中是对单个参数 "x" 进行操作的逻辑。

注意,不能将无参重载版本去掉而改成下面这样:

template<class T, ...Args>
Type Func(T& x, Args... args)
{// ...if(sizeof...(args) != 0)Func(args...);
}

假如按照这样的方式来写,无参的Func虽然不会被调用,但是在编译阶段,编译器会尝试实例化出无参的版本。

而我们给出的模板至少接收一个参数,编译器无法实例化出无参Func,会直接报错。 

通过这样的方式,我们可以将参数包中的参数从前向后一个一个地拆出来,分别操作。

例如:

void ShowList()
{// 编译器时递归的终⽌条件,参数包是0个时,直接匹配这个函数cout << endl;
}template <class T, class ...Args>
void ShowList(T x, Args... args)
{cout << x << " ";// args是N个参数的参数包// 调⽤ShowList,参数包的第⼀个传给x,剩下N-1传给第⼆个参数包ShowList(args...);
}// 编译时递归推导解析参数
template <class ...Args>
void Print(Args... args)
{ShowList(args...);
}int main()
{Print();Print(1);Print(1, string("xxxxx"));Print(1, string("xxxxx"), 2.2);return 0;
}

也可一次拆出多个参数,在递归函数中增加几个形参即可。但此时就需要多写几个重载函数来处理参数个数不够多的情况了。 

复合函数

template <class T>
const T& GetArg(const T& x)
{// ...// 返回值不一定是x,可根据需要确定return x;
}template <class ...Args>
void Arguments(Args... args)
{}template <class ...Args>
void test(Args... args)
{// 注意GetArg必须返回或者到的对象,这样才能组成参数包给ArgumentsArguments(GetArg(args)...);
}

"Arguments(GetArg(args)...);" 的含义是,将参数包中的参数依次传入GetArg函数中进行调整之后,形成新的参数包传给Arguments函数。

这样的写法不仅可以用于展开参数包,还可以用于需要对参数包每个参数都进行处理之后进行函数调用的场景。

注意:Arguments函数的调用是必要的,括号内的 "GetArg(args)..." 并不能独立作为表达式而存在。

3. emplace系列接口

template <class... Args> 
void emplace_back (Args&&... args);template <class... Args> 
iterator emplace (const_iterator position, Args&&... args);

C++11以后STL容器新增了empalce系列的接口,empalce系列的接口均为模板可变参数,能够完成与push_back,insert等相同的功能,而参数包的存在使得emplace系列接口不止可以接受要插入的对象,还可以接受构造要插入对象的参数。

以前我们在进行插入时,要么用已有对象,要么用匿名对象,要么用隐式类型转换,总之在进入函数之前,对象就需要被构造好,进入函数之后再通过拷贝构造或移动构造,构造出形参进行插入。

而emplace系列接口的参数包可以直接接受构造对象的参数而不用发生隐式类型转换,在函数内部直接将参数包层层传递(如果复用了其他函数的话)给构造函数构造出对象,并进行插入。

template <class... Args>
void emplace_back(Args&&... args)
{insert(end(), std::forward<Args>(args)...);
}template <class... Args>
iterator emplace(const_iterator pos, Args&&... args)
{Node* cur = pos._node;Node* newnode = new Node(std::forward<Args>(args)...);Node* prev = cur->_prev;// prev newnode curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);
}

也就是说emplace系列接口可以将函数外的构造转移到函数内进行构造,以此节省掉一次拷贝构造或移动构造。

emplace系列总体而言是更高效的,推荐以后使用emplace系列替代insert和push系列。

#include<list>
int main()
{list<string> lt;// 传左值,跟push_back一样,走拷贝构造string s1("111111111111");lt.emplace_back(s1);// 右值,跟push_back一样,走移动构造lt.emplace_back(move(s1));// 直接把构造string参数包往下传,直接用string参数包构造string// 这里达到的效果是push_back做不到的lt.emplace_back("111111111111");list<pair<string, int>> lt1;// 跟push_back一样// 构造pair + 拷贝/移动构造pair到list的节点中data上pair<string, int> kv("苹果", 1);lt1.emplace_back(kv);// 跟push_back一样lt1.emplace_back(move(kv));// 直接把构造pair参数包往下传,直接用pair参数包构造pair// 这⾥达到的效果是push_back做不到的lt1.emplace_back("苹果", 1);return 0;
}

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

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

相关文章

BSV区块链为供应链管理带来效率革命

​​发表时间&#xff1a;2024年10月10日 供应链管理是众多行业的重中之重&#xff0c;它确保了商品能够从制造商处顺畅地传递到消费者手中。然而&#xff0c;传统的供应链管理面临着许多挑战&#xff0c;包括缺乏透明度、延误、欺诈和协调上的低效率等等。 BSV区块链技术的出…

Linux基础(七):Linux文件与目录管理

Linux文件与目录管理 1.目录与路径1.1 cd1.2 pwd1.3 mkdir1.4 rmdir1.5 ls1.6 cp1.7 rm1.8 mv 2.可执行文件路径的变量&#xff1a;$PATH3.从字符串中获取目录名称和文件名称4.文件内容读取4.1 cat与tac4.2 nl4.3 more和less4.4 head与tail4.5 od 5 使用touch来改变文件的时间6…

C语言初阶:十一.代码调试技巧

❤欢迎各位大佬访问&#xff1a;折枝寄北-CSDN博客折枝寄北擅长C语言初阶,等方面的知识,折枝寄北关注python,c,java,qt,c语言领域.https://blog.csdn.net/2303_80170533?typeblog❤文章所属专栏https://blog.csdn.net/2303_80170533/category_12794764.html?spm1001.2014.300…

什么是aps排产管理软件?aps排产管理软件有什么用?最详细解释!

近几年&#xff0c;APS排产管理软件特别火&#xff0c;很多制造业企业都在谈论和使用它。不过&#xff0c;因为信息太多太杂&#xff0c;我们平时接收到的往往都是零零碎碎的介绍&#xff0c;很难全面了解它。所以&#xff0c;今天这篇文章就来给大家做个整合&#xff0c;把APS…

微信小程序25__实现卡片变换

先看效果图 实现代码如下&#xff1a; <view class"page" style"filter:hue-rotate({{rotation}}deg)"><view class"prev" catchtap"toPrev">《《《</view><view class"next" catchtap"toNext&q…

vue项目中如何在路由变化时增加一个进度条

在 Vue.js 项目中&#xff0c;使用路由&#xff08;如 Vue Router&#xff09;时&#xff0c;为了提升用户体验&#xff0c;你可能会想要在路由变化时显示一个进度条。这可以通过多种方式实现&#xff0c;其中一种流行的做法是使用第三方库&#xff0c;如 vue-loading-bar 或 n…

4款专业音频在线剪辑工具帮你开启创意之路。

音频在线剪辑工具能够为我们提供很大的便利&#xff0c;对于不管是专业的音乐制作人还是音频创作爱好者来说&#xff0c;都能借助一些音频编辑工具来充分发挥自己的创意。所以这一次&#xff0c;我要给大家介绍几个专业方便的音频剪辑工具。 1、福昕音频在线 直达链接&#x…

JK触发器(Quartus与Modelsim联合仿真)

JK触发器由于其灵活的逻辑功能&#xff0c;被广泛应用于数字电路设计中&#xff0c;如计数器、寄存器、序列信号发生器等。它可以通过改变J和K的输入来实现不同的逻辑操作&#xff0c;使得设计更加简洁高效。 在数字电子技术中&#xff0c;JK触发器的真值表是理解其工作原理和设…

Redis高频面试题

一、Redis有什么好处? 高性能:Redis是一个基于内存的数据存储系统,相比于传统的基于磁盘的数据库系统,它能够提供更高的读写性能。支持丰富的数据类型:Redis支持多种数据结构,包括字符串、哈希、列表、集合、有序集合等,这使得它可以用于多种不同的应用场景。持久化:Re…

Javaweb 实验7 JSP内置对象II实现购物车

实验七 JSP内置对象II 目的&#xff1a; 掌握JSP内置对象的使用。理解JSP的作用域掌握session&#xff0c;application对象的使用 实验要求&#xff1a; 完成实验题目要求提交实验报告&#xff0c;将代码和实验结果页面截图放入报告中 实验过程&#xff1a; 一、结合之前…

Linux随记(十二)

一、redhat6.9 &#xff0c;sftp连接后出现2个connect close 问题描述&#xff1a; 操作系统&#xff1a;redhat 6.9 客户反馈&#xff0c;他们机器sftp连接jxx192.168.1.100后出现connection closed排查过程&#xff1a; 登录192.168.1.100 &#xff08;最开始使用普通用户…

强大的文本编辑器Notepad++8.4.6 最新版

Notepad最新版是一款多功能的代码编辑工具。Notepad官方版支持27种编程语言&#xff0c;涵盖C、C 、Java 、C#,、XML、 HTML,、PHP、python等等&#xff0c;能够帮助程序员提高编辑效率。Notepad软件支持python与sql代码高亮功能&#xff0c;并且免费开源&#xff0c;能够完美地…

【测试平台】打包 子节点android环境配置

背景 本文记录不是安卓Gradle打包&#xff0c;因为我们google play提审&#xff0c;为了规避跟下架包的相似度避免马甲包封号&#xff0c;使用混淆逻辑。 打包环境部署 申请对应虚拟机会有两个账号root和admin&#xff0c;主要避免root账号权限过高造成脚本误操作。这里面问题…

免杀对抗—DLL劫持白加黑隐写分离EDRSyscall-hook

前言 今天讲点比较高端的东西—DLL反射注入&#xff0c;首先什么是DLL文件&#xff0c;简答来说就是程序为了实现某个功能而调用的文件。举个例子&#xff0c;某个代码想要实现某个功能是不是会调用一些封装好的函数&#xff0c;exe同样如此&#xff0c;想要实现某个功能就会调…

uniapp推送配置流程

Dcloud Dcloud注册账号 个推 了解即可 注册个推账号 ios配置流程 需配置含有推送的描述文件以及p8证书 配置推送证书 ios证书配置报技术错误&#xff08;参数错误&#xff09; TeamID-苹果开发者账号唯一的ID 安卓需配置多厂商 小米手机需要配置小米厂商 华为手机则需…

zookeeper全系列学习之分布式锁实现

文章目录 前言一、分布式锁的通用实现思路二、ZK实现分布式锁的思路三、ZK实现分布式锁的编码实现1、核心工具类实现2、测试代码编写线程安全问题复现使用上面封装的ZkLockHelper实现的分布式锁 优点缺点 总结 前言 就像上篇文章zookeeper全系列学习之统一配置获取说的&#x…

Uni-App-02

条件编译 条件编译概念 不同的运行平台终归有些专有的特性&#xff0c;无法实现跨平台完全兼容&#xff0c;例如&#xff1a;微信小程序导航栏右上角的关闭图标。 uni-app提供了一种“条件编译”机制&#xff0c;可以针对特定的平台编译执行特定的代码&#xff0c;否则不执行。…

【ArcGISPro】制作简单的ArcGISPro-AI助手

【python】AI Navigator的使用及搭建本机大模型_anaconda ai navigator-CSDN博客 【Python】AI Navigator对话流式输出_ai大模型流式输出效果(打字效果) python-CSDN博客 【C#】调用本机AI大模型流式返回_怎么实现调用本地大模型时实现流式输出-CSDN博客 【ArcGISPro】宣布推…

springboot 修复 Spring Framework 特定条件下目录遍历漏洞(CVE-2024-38819)

刚解决Spring Framework 特定条件下目录遍历漏洞&#xff08;CVE-2024-38816&#xff09;没几天&#xff0c;又来一个新的&#xff0c;真是哭笑不得啊。 springboot 修复 Spring Framework 特定条件下目录遍历漏洞&#xff08;CVE-2024-38816&#xff09;https://blog.csdn.ne…

可编辑97页PPT | 制造企业数字化转型战略咨询及IT总体规划方案

荐言分享&#xff1a;制造企业数字化转型是当前市场环境下的必然趋势&#xff0c;旨在通过引入先进的信息技术&#xff0c;优化业务流程&#xff0c;提升运营效率&#xff0c;增强客户体验&#xff0c;实现可持续发展。这一转型过程涉及多个方面&#xff0c;需要综合考虑企业战…