C++可变参技巧揭秘:从函数到模板,一网打尽

最近在用C++开发项目过程中,遇到了场景需要用可变参(...)的情况,发现开发业务相关同事,对这块理解不是很清晰,遂对此进行梳理总结,以便业务相关同事学习,以及后续遇到时的参考。

对此不是很清楚的读者也可参考看下。

文章目录

    • 1. 变参函数(C风格)
      • 例子1 可变参为数值类变量
      • 例子2 可变参为字符串
    • 2. 函数模板可变参(variadic templates)
    • 3. 模板类使用可变参数
    • 4. 折叠表达式(fold expressions) (C++17 引入)

1. 变参函数(C风格)

在C++中,开发者可以使用C风格的可变参函数来构建一个能接受任意数量参数的函数。这种方式允许函数处理不确定数量的参数,正如C语言中的printf函数一样。C风格的可变参函数依赖于<cstdarg>头文件,这个头文件提供了一组宏来操作可变参数列表。主要的宏包括va_startva_argva_endva_copy

下面我们通过两个例子来看可变参函数的使用情况。

例子1 可变参为数值类变量

下面是一个基本的例子,展示如何使用C风格的可变参函数实现一个简单的函数,这个函数接受任意数量的整数参数,并计算它们的和:

#include <cstdarg>
#include <iostream>// 定义一个计算任意个整数和的函数
int sum(int n, ...) {int total = 0;va_list args;va_start(args, n); // 初始化args为参数列表,n是参数个数for(int i = 0; i < n; ++i) {total += va_arg(args, int); // 使用va_arg获取下一个参数的值}va_end(args); // 清理工作return total;
}int main() {int result = sum(5, 1, 2, 3, 4, 5); // 可以传入任意数量的参数std::cout << "The sum is: " << result << std::endl;return 0;
}

输出将会是:

The sum is: 15

在上述代码中,sum函数的第一个参数n是后续参数的个数,之后的参数被视为一个可变参数列表。可变参数列表使用va_list类型的变量args来访问。我们首先使用va_start宏初始化args,以使其指向第一个可变参数。接着,通过在循环中反复调用va_arg宏来逐一访问每个参数;此宏的第二个参数表明了当前要访问的参数的类型。最后,使用va_end宏对args进行清理工作。

例子2 可变参为字符串

如果开发者想要使用C风格的可变参函数来输出字符串,类似于C语言中的printf函数,开发者可以使用vprintf函数。vprintfprintf类似,但它接收一个va_list类型的参数来处理可变数量的参数。

下面是一个例子,演示了如何实现一个可变参函数来记录日志信息:

#include <cstdarg>
#include <cstdio>// 定义一个日志函数
void log_message(const char* format, ...) {va_list args;// 开始处理可变参数va_start(args, format);// vprintf使用提供的格式化字符串和可变参数列表进行输出vprintf(format, args);// 输出换行printf("\n");// 清理可变参数列表va_end(args);
}#define LOG_MSG(...) log_message(__VA_ARGS__)int main() {LOG_MSG("This is a %s message", "test");LOG_MSG("Today is %s, %d degrees celsius", "Tuesday", 25);// log_message("This is a %s message", "test");// log_message("Today is %s, %d degrees celsius", "Tuesday", 25);return 0;
}

输出将会是:

This is a test message
Today is Tuesday, 25 degrees celsius

在上述代码中,log_message函数接受了一个format字符串,这个字符串包含了格式化信息。这些格式编码指示了如何打印后续的参数(例如,%s表示字符串,%d表示整数)。函数用va_start宏初始化va_list类型的变量args,然后使用vprintf将格式化的字符串与可变参数一起输出。最后用va_end宏清理可变参数。

2. 函数模板可变参(variadic templates)

C++中的函数模板可变参允许开发者创建接受任意数量参数的函数模板,甚至可以是不同类型的参数。这是通过模板参数包(template parameter pack)和函数参数包(function parameter pack)来实现的。在C++11及以后的版本中,使用省略号...来表示参数包。

下面是一个使用函数模板可变参的例子,使用递归函数模板打印出可变数量的参数:

#include <iostream>// 基本函数模板,用于终止递归
void print() {// 当没有参数时,就什么都不做,终止递归std::cout << "No more arguments." << std::endl;
}// 可变参数模板,用于打印一个参数然后递归调用自身以打印剩余参数
template<typename T, typename... Args>
void print(T firstArg, Args... args) {std::cout << firstArg << std::endl; // 打印第一个参数print(args...); // 递归调用自身以打印剩余参数
}int main() {print(1, 2.5, "three");return 0;
}

输出将是:

1
2.5
three
No more arguments.

在上面的代码中,print函数模板使用两个参数:第一个是类型为TfirstArg,表示当前函数需要处理的第一个参数;第二个是一个函数参数包args...,代表其余的参数。在函数模板内部,第一个参数被打印出来,然后通过递归调用print函数模板来处理剩余的参数。为了终止递归,我们提供了一个重载的print函数,它不接受任何参数。当参数包中没有剩余元素时,会调用这个重载的函数。

3. 模板类使用可变参数

C++中的模板类使用可变参,允许编写能够接受任意数量类型参数的类模板。它通过使用模板参数包(template parameter pack)实现,这在实现泛型编程时非常有用。

下面是一个使用了可变参的模板类示例,我们将创建一个简单的元组类MyTuple,用于存储任意数量和类型的元素:

#include <iostream>
#include <string>// 前向声明,用于递归展开模板参数包
template<typename... Ts>
class MyTuple;// 专门化终止条件
template<>
class MyTuple<> {};// 主模板
template<typename T, typename... Ts>
class MyTuple<T, Ts...> {T head;MyTuple<Ts...> tail;public:MyTuple(T head, Ts... tail) : head(head), tail(tail...) {}// 获取第一个元素T getHead() const { return head; }// 获取尾部元组MyTuple<Ts...> getTail() const { return tail; }// 打印第一个元素和递归打印剩余元素void print() const {std::cout << head << " ";tail.print();}
};// 递归终止条件的打印函数
template<>
void MyTuple<>::print() const {}int main() {MyTuple<int, double, std::string> myTuple(1, 2.3, "Hello World");myTuple.print();return 0;
}

在这个示例中,MyTuple是一个模板类,可以接受任意数量和任意类型的参数。这是通过在类定义时使用模板参数包typename... Ts实现的。模板参数包是C++11引入的特性,它允许函数或类模板接受不确定数量的模板参数。

MyTuple类内部,我们使用了递归组合的方式来处理这些参数,其中每个MyTuple对象存储一个元素(head)和一个剩余元素的元组(tail)。通过这种方式,我们可以实现getHead()getTail()成员函数,分别用于获取头部元素和尾部元组。

最后,我们通过特化一个空的MyTuple<>类和提供一个递归终止条件下的print方法,来处理递归展开的终点情况。

运行上述程序的输出将会是:

1 2.3 Hello World 

4. 折叠表达式(fold expressions) (C++17 引入)

在C++中,折叠表达式是C++17标准中引入的一项功能,它允许程序员对包展开时产生的所有参数进行一个二元操作。这大大简化了可变参数模板函数或类模板的编写,因为开发者不再需要写递归模板或递归函数来处理参数包。

一个简单的例子可以解释如何使用折叠表达式:

#include <iostream>// 可变参数模板函数,使用折叠表达式来计算所有参数之和
template<typename... Args>
auto sum(Args... args) {return (... + args);
}int main() {std::cout << "Sum of 1, 2, 3 is " << sum(1, 2, 3) << std::endl;std::cout << "Sum of 10, 20, 30, 40 is " << sum(10, 20, 30, 40) << std::endl;return 0;
}

这个sum函数通过折叠表达式计算了任意数量整数参数的和。这里的(... + args)是一个展开表达式,它将传递给sum的所有参数进行累加操作。

在这个表达式中:

  • ... 表示展开操作符
  • + 是要应用的二元操作
  • args 是参数包

折叠表达式有两种形式:

  1. 二元左折叠(Binary Left Fold):(op ... args),它展开为((arg1 op arg2) op ...) op argN
  2. 二元右折叠(Binary Right Fold):(args ... op),它展开为arg1 op (arg2 op ... (argN-1 op argN))

运行上述代码将会得到以下输出:

Sum of 1, 2, 3 is 6
Sum of 10, 20, 30, 40 is 100

这展示了折叠表达式是处理可变参数模板函数一个非常强大且简洁的方法。它不仅限于求和,还可以用于其它任何二元操作,如乘法、逻辑操作等。

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

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

相关文章

第十三期Big Demo Day亮点项目:CCarbon重塑碳交易生态,助力全球绿色发展

第十三期Big Demo Day活动即将于2024年5月28日在香港数码港的CyberArena隆重举行。我们荣幸地宣布&#xff0c;利用区块链技术优化全球碳交易CCarbon项目将亮相&#xff0c;参与精彩的项目路演。本次活动由ZeeprLabs、BiKing Exchange、Gather冠名赞助&#xff0c;Central Rese…

DOS学习-目录与文件应用操作经典案例-ren

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一.前言 二.使用 三.案例 案例 1&#xff1a;重命名当前目录下的文件 案例 2&#xff1a…

自定义函数与结构体

定义一个结构体 Point&#xff0c;其中包含两个 double 类型的成员 x 和 y&#xff0c;用来表示一个二维坐标点。接着定义一个函数 dist&#xff0c;用来计算两个点之间的欧几里德距离。 在函数中&#xff0c;通过调用 hypot 函数来计算两点之间的距离&#xff0c;C 中的hypot…

超清高帧,成像升级 | SWIR短波红外相机500万像素992芯片

博图光电5MP短波红外相机&#xff0c;搭载了索尼IMX992 SenSWIR传感器&#xff0c;支持5.2MP分辨率&#xff0c;适合探测波长在400nm-1700nm波段的可见光和短波红外光&#xff0c;有效面积和透光率得到提升&#xff0c;内置TEC制冷片&#xff0c;实现了像素尺寸和图像均匀性方面…

OSPF不规则区域

一、OSPF不规则区域 1、远离了骨干的非骨干 2、不连续骨干 解决目的--全网可达 解决方案: 1、tunnel-- 在合法与非法 ABR 间使用 tunnel建立一个新的网段(类似连接一条独立的网线);然后将该网段宣告到ospf协议中; 缺点: 1)选路不佳 2)周期和触发信息对中间穿越区域造成影响…

网络实时安全:构筑数字时代的铜墙铁壁

什么是网络实时安全&#xff1f; 网络实时安全&#xff0c;简而言之&#xff0c;是一种能够在威胁发生的瞬间即刻识别、响应并有效抵御的安全机制。它强调的是速度与效率&#xff0c;确保网络环境能够持续处于安全状态。这背后&#xff0c;离不开高科技的支撑——扩展检测系统…

Linux源码编译安装MySQL + Qt连接MySQL

一、准备工作 1. 编译环境&#xff1a; 银河麒麟V10 飞腾D2000 CPU 2. 下载MySQL源码 这里编译的是5.7.44版本&#xff0c;带Boost库&#xff0c;这是官网的下载地址&#xff1a;MySQL :: Download MySQL Community Server (Archived Versions) 3. 解压压缩包 tar -zxvf mys…

Python内置方法串讲:类型转化与实用技巧

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、类型转化&#xff1a;从A到B的魔法 二、实用技巧&#xff1a;避免类型错误 三、总结 一…

Datawhale团队第十一期录取名单!

Datawhale团队 公示&#xff1a;Datawhale团队成员 Datawhale成立五年了&#xff0c;从一开始的12个人&#xff0c;学习互助&#xff0c;到提议成立开源组织&#xff0c;做更多开源的事情&#xff0c;帮助更多学习者&#xff0c;也促使我们更好地成长。于是有了我们的使命&…

一年前的Java作业,模拟游戏玩家战斗

说明&#xff1a;一年前写的作业&#xff0c;感觉挺有意思的&#xff0c;将源码分享给大家。 刚开始看题也觉得很难&#xff0c;不过写着写着思路更加清晰&#xff0c;发现也没有想象中的那么难。 一、作业题目描述&#xff1a; 题目&#xff1a;模拟游戏玩家战斗 1.1 基础功…

清华大学 | 机器人实验室 | 机械结构 | 招聘

招聘实习生、正式工程师&#xff0c;请发送简历&#xff0c;具体工作内容、福利待遇详细沟通 基本要求 1. 熟练使用3D建模软件(如SolidWorks、CATIA、CAD等) 2. 有过机器人项目经验&#xff0c;参加过机器人相关比赛者优先 3. 具备静力学仿真经验&#xff0c;熟悉有限元分析…

假象和谎言

原创 | 刘教链 隔夜BTC&#xff08;比特币&#xff09;徘徊在69k一线。5.25教链内参报告&#xff0c;《BTC ETF持仓即将超越中本聪》。ETH ETF的尘嚣逐渐散去&#xff0c;复归于平静。戏刚唱了个开头&#xff0c;结尾还留着悬念。4000刀之于ETH看来是个关键阻力位&#xff0c;最…

element ui 的密码输入框点击显示隐藏密码时,图标随之改变

场景图&#xff1a; 原理&#xff1a; 通过修改el-input框的type属性&#xff0c;来设置显示或者隐藏。从而改变图标地址。 <el-input class"passwordinput" :type"pwdObj.pwdType" ref"pwdInput" placeholder"密码"v-model"…

Vue热更新出现内存溢出

Vue热更新出现内存溢出 vue-cli2遇到此问题的解决办法&#xff1a;vue-cli3遇到此问题的解决办法&#xff1a;方法一&#xff08;已测试ok&#xff09;方法二&#xff08;未尝试&#xff09; 开发项目有一段时间了&#xff0c;随着项目越来越大&#xff0c;打包的时间也相应的变…

若依微服务实现分布式事务

一、基本介绍 1、什么是分布式事务 指一次大的操作由不同的小操作组成的&#xff0c;这些小的操作分布在不同的服务器上&#xff0c;分布式事务需要保证这些小操作要么全部成功&#xff0c;要么全部失败。从本质上来说&#xff0c;分布式事务就是为了保证不同数据库的数据一致…

ssm137基于SSM框架的微博系统+vue

微博系统网站的设计与实现 摘 要 网络技术和计算机技术发展至今&#xff0c;已经拥有了深厚的理论基础&#xff0c;并在现实中进行了充分运用&#xff0c;尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代&#xff0c;所以对于信息的宣传和管理就…

家用洗地机哪个品牌好?家用洗地机排行榜前十名

随着洗地机逐渐进入大众视野&#xff0c;这种集吸、拖、洗功能于一体的清洁工具&#xff0c;凭借其高效便捷的特点&#xff0c;成为家庭清洁的新宠。洗地机不仅能够减少地面清洁时间&#xff0c;节省体力&#xff0c;还能提高清洁效果。然而&#xff0c;面对琳琅满目的洗地机品…

[学习笔记](Python3)防止SQL注入、XSS攻击和文件上传漏洞

学习笔记&#xff1a;防止SQL注入、XSS攻击和文件上传漏洞&#xff08;Python3&#xff09; 本笔记由生成式大模型GPT-4o自动整理。注意AI可能犯错。代码和理论由GPT-4o(2024-5-21)自行撰写未经人工复核。 参数化查询防SQL注入 参数化查询通过将SQL语句和数据分离来防止SQL注…

Web会话管理

一、会话管理的概念&#xff1a; 在人机交互时&#xff0c;会话管理是保持用户的整个会话活动的互动与计算机系统跟踪过程会话管理分类: 桌面会话管理、浏览器会话管理、Web服务器的会话管理。 二、为什么需要会话管理&#xff1f; HTTP是一种无状态协议&#xff0c;一次请…

Vue.js 混入(Mixins)高级用法:提升代码复用与灵活性

Vue.js 中的混入&#xff08;Mixins&#xff09;是一种强大而灵活的设计模式&#xff0c;它允许你将可复用的组件功能抽离为独立的模块&#xff0c;并在多个组件间共享。本文将深入探讨混入的高级用法&#xff0c;包括如何传递参数给混入、解决命名冲突、以及如何利用高阶组件思…