C 和 C++ 可变参数介绍

文章目录

  • 前言
  • 概念
  • C 的可变参数
    • 参数列表 #va_list 4组宏
  • C++ 的可变参数
    • 参数列表 #va_list 4组宏
    • 初始化列表 initializer_list<> 类模板
    • 可变参数模板
  • 总结
  • 参考资料
  • 作者的话

前言

C 和 C++ 可变参数介绍。


概念

可变(长)/不定(长)参数:函数可以接收任意数量的参数(函数在声名和定义时不明确参数的数量)


C 的可变参数

参数列表 #va_list 4组宏

头文件

  • <stdarg.h>

  • #va_list:类型宏;参数列表
  • #va_start():函数宏;va_list 指向参数列表的第一个参数
  • #va_arg():函数宏;依据类型,va_list 指向参数列表的下一个参数
  • #va_end():函数宏;清理 va_list

底层原理

  • #va_list:字符指针
  • #va_start():指针指向第一个元素
  • #va_arg():指针指向下一个元素
  • #va_end():指针置空

缺点

  • 代码逻辑需要明确参数的数量和每个参数的类型

代码示例

#include <stdarg.h> // #va_list、#va_start()、#va_arg()、#va_end()
#include <stdio.h>// 形参的一般形式:
// num:参数数量
// ...:参数列表
void print(int num, ...)
{// 1. 定义 va_listva_list para_list; // 类型宏;参数列表// 2. 初始化 va_listva_start(para_list, num); // 函数宏;va_list 指向参数列表的第一个参数// 3. 遍历 va_listfor (int i = 0; i < num; ++i){printf("%d ", va_arg(para_list, int)); // 函数宏;依据类型,va_list 指向参数列表的下一个参数}printf("\n");// 4. 清理 va_listva_end(para_list); // 函数宏;清理 va_listreturn;
}int main()
{print(2, 0, 1);// 实参的一般形式:// 2:参数数量// 0 1:参数列表print(3, 0, 1, 2);return 0;
}
// 输出:
// 0 1
// 0 1 2

C++ 的可变参数

参数列表 #va_list 4组宏

见 “C 的可变参数” 内容。

头文件

  • <cstdarg>

初始化列表 initializer_list<> 类模板

头文件

  • <initializer_list>

原理

  • 类比容器 vector<>
  • 比容器轻量
  • 封装参数(指向参数的指针、参数的数量和参数的类型等)的包装器/对象

缺点

  • 代码逻辑需要明确参数的类型
  • 一个 initializer_list<> 对象只支持一种类型(可以使用多个 initializer_list<> 对象按序支持多种类型)

按序:如一个 initializer_list<int> 对象表示一部分参数都是 int 类型,另一个 initializer_list<string> 对象表示另一部分参数都是 string 类型;不能是一个 initializer_list<int> 对象表示一部分参数既有 int 类型又有 string 类型

代码示例

// #include <initializer_list> // initializer_list<>
#include <iostream>using std::cout;
using std::endl;
using std::initializer_list;void print(initializer_list<int> li) // 使用 initializer_list<> 对象接收可变参数
{for (const int l : li){cout << l << " ";}cout << endl;return;
}int main()
{print({0, 1}); // 使用列表初始化创建匿名 initializer_list<> 对象并作为参数print({0, 1, 2});return 0;
}
// 输出:
// 0 1
// 0 1 2

可变参数模板

相关语法

  • typename…:定义模板参数包
  • Args:模板参数(抽象概念) 包的名称,可自定义名称,表示任意类型和数量的模板参数
  • Args…:模板参数包
  • args:具体参数(具体概念) 包的名称,可自定义名称,表示任意类型和数量的具体参数
  • args…:展开具体参数包
  • sizeof…(具体参数包):获取具体参数包参数的数量
  • …:折叠表达式

折叠表达式的概念和语法较复杂 (作者觉得很怪异),在此不深入讲解。
可参见:(C++模板编程):折叠表达式、可变参表达式_c++模板折叠-CSDN博客

解包方式

  • 递归展开1
  • 递归展开2(C++ 17支持)
  • 逗号表达式展开1
  • 逗号表达式展开2(优化)
  • 逗号表达式3(优化)
  • 折叠表达式展开(C++ 17支持)

缺点

  • 概念较复杂
  • 语法较复杂

获取具体参数包参数的数量

#include <iostream>using std::cout;
using std::endl;template <typename... Args>
void print(Args... args)
{cout << sizeof...(args) << endl;return;
}int main()
{print(0, 'c'); // 2个不同类型的参数print(0, 'c', "str"); // 3个不同类型的参数return 0;
}
/*
输出:
2
3逐行解释:
2:具体参数包参数的数量是2
3:具体参数包参数的数量是3
*/

递归展开1

#include <iostream>using std::cout;
using std::endl;// 参数数量 == 1的函数模板
// 递归终止时调用
template <typename T>
void print(T value)
{cout << value << endl; // 参数值return;
}// 可变参数模板
// 参数数量 > 1的函数模板
// 递归时调用
template <typename T, typename... Args>
void print(T value, Args... args)
{cout << value << " "; // 参数值print(args...); // 递归调用return;
}int main()
{print(0, 'c');        // 2个不同类型的参数print(0, 'c', "str"); // 3个不同类型的参数return 0;
}
/*
输出:
0 c
0 c str
*/

递归展开2(C++ 17支持)

#include <iostream>using std::cout;
using std::endl;// 可变参数模板
// 参数数量 >= 1的函数模板
template <typename T, typename... Args>
void print(T value, Args... args)
{cout << value << " "; // 参数值// 参数数量为0时无法递归调用:print(args...);,需要递归终止// C++ 17标准支持“if constexpr()”语法,可以在编译而不是运行时求值以终止递归,使得编译通过if constexpr (sizeof...(args) > 0) // 递归调用{print(args...);}else // 递归终止{cout << endl;}return;
}int main()
{print(0, 'c');        // 2个不同类型的参数print(0, 'c', "str"); // 3个不同类型的参数return 0;
}
/*
输出:
0 c
0 c str
*/

逗号表达式展开1

#include <iostream>
// #include <initializer_list>  // initializer_list<>using std::cout;
using std::endl;
using std::initializer_list;// 可变参数模板
// 参数数量 >= 1的函数模板
template <typename T, typename... Args>
void print(T value, Args... args)
{cout << value << " "; // 第一个参数值// 重点理解:// [args]{cout << args << " ";}:Lambda 表达式// [args]{cout << args << " ";}():调用 Lambda 表达式// value:第一个参数的值// (,):逗号表达式:先计算左表达式,再计算右表达式,结果是右表达式的值// ([args]{cout << args << " ";}(), value):先调用 Lambda 表达式,再计算第一个参数的值,结果是第一个参数的值// args...:展开具体参数包// ([args]{cout << args << " ";}(), value)...:展开具体参数包,对每一个参数,先调用 Lambda 表达式,再计算第一个参数值,结果是第一个参数值// typename T:第一个参数的类型// initializer_list<>{}:initializer_list<> 对象// initializer_list<T>{}:匿名 initializer_list<> 对象,值类型是第一个参数的类型// initializer_list<T>{([args]{cout << args << " ";}(), value)...};:第一个参数作为匿名 initializer_list<> 对象的值,值类型是第一个参数的类型// C++11 和 C++14 标准,没有提供一种直接将具体参数包展开到函数调用参数列表中的语法// 所以可以使用 initializer_list<> 结合 args... 展开具体参数包// 又因为 initializer_list<> 的值需要相同类型// 所以可以使用逗号表达式,无论左表达式怎么计算,都返回第一个参数的类型和值 T value// 所以函数模板需要定义 typename T,函数需要定义 T valueinitializer_list<T>{([args]{ cout << args << " "; }(),value)...};cout << endl;return;
}int main()
{print(0, 'c');        // 2个不同类型的参数print(0, 'c', "str"); // 3个不同类型的参数return 0;
}
/*
输出:
0 c
0 c str
*/

逗号表达式展开2(优化)

#include <iostream>
// #include <initializer_list>  // initializer_list<>using std::cout;
using std::endl;
using std::initializer_list;// 可变参数模板
// 参数数量 >= 1的函数模板
// 依据“逗号表达式展开1”的分析,模板参数 typename T、初始化列表 initializer_list<> 的类型 T、第一个参数值 value 和逗号表达式的右表达式 value 有意义但无用途,可以优化
template <typename... Args>
void print(Args... args)
{initializer_list<int>{([args]{ cout << args << " "; }(),0)...};cout << endl;return;
}int main()
{print(0, 'c');        // 2个不同类型的参数print(0, 'c', "str"); // 3个不同类型的参数return 0;
}
/*
输出:
0 c
0 c str
*/

逗号表达式展开3(优化)

#include <iostream>
#include <vector>using std::cout;
using std::endl;
using std::vector;// 可变参数模板
// 参数数量 >= 1的函数模板
// 依据“逗号表达式展开1”的分析,对于可以使用列表初始化 {} 的对象,数组和向量 vector<> 等,可以结合 args... 展开具体参数包
template <typename... Args>
void print(Args... args)
{int arr[]{([args]{ cout << args << " "; }(),0)...};cout << endl;vector<int>{([args]{ cout << args << " "; }(),0)...};cout << endl;return;
}int main()
{print(0, 'c');        // 2个不同类型的参数print(0, 'c', "str"); // 3个不同类型的参数return 0;
}
/*
输出:
0 c
0 c
0 c str
0 c str
*/

折叠表达式展开(C++ 17支持)

#include <iostream>using std::cout;
using std::endl;// 可变参数模板
// 参数数量 >= 1的函数模板
template <typename... Args>
void print(Args... args)
{// 二元左折叠表达式(概念复杂)// (,):逗号表达式:连接折叠表达式和操作// 对每一个参数,先输出参数,再输出空格(..., (cout << args << ' '));cout << endl;
}int main()
{print(0, 'c');        // 2个不同类型的参数print(0, 'c', "str"); // 3个不同类型的参数return 0;
}
/*
输出:
0 c
0 c str
*/

总结

C 和 C++ 可变参数介绍。


参考资料

  • C 可变参数 | 菜鸟教程 (runoob.com)
  • 02_可变长参数的基础_哔哩哔哩_bilibili
  • va_list原理及用法-CSDN博客
  • C++ 实现可变参数的三个方法 - Ofnoname - 博客园 (cnblogs.com)
  • 第 2 章 语言可用性的强化 现代 C++ 教程: 高速上手 C++ 11/14/17/20 - Modern C++ Tutorial: C++ 11/14/17/20 On the Fly (changkun.de)
  • c++11之函数参数包展开 - mohist - 博客园 (cnblogs.com)
  • (C++模板编程):折叠表达式、可变参表达式_c++模板折叠-CSDN博客

作者的话

  • 感谢参考资料的作者/博主
  • 作者:夜悊
  • 版权所有,转载请注明出处,谢谢~
  • 如果文章对你有帮助,请点个赞或加个粉丝吧,你的支持就是作者的动力~
  • 文章在描述时有疑惑的地方,请留言,定会一一耐心讨论、解答
  • 文章在认识上有错误的地方, 敬请批评指正
  • 望读者们都能有所收获

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

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

相关文章

10道高频Vuex面试题快问快答

※其他的快问快答&#xff0c;看这里&#xff01; 10道高频Qiankun微前端面试题快问快答 10道高频webpack面试题快问快答 20道高频CSS面试题快问快答 20道高频JavaScript面试题快问快答 30道高频Vue面试题快问快答 面试中的快问快答 快问快答的情景在面试中非常常见。 在面试过…

NOIP2023模拟14联测35 charlotte

题目大意 给你一棵有 n n n个节点的树&#xff0c;并用 01 01 01串告诉你哪些节点上有棋子&#xff08;恰好一棵&#xff09;。 你可以进行若干次操作&#xff0c;每次操作可以将两颗距离至少为 2 2 2的棋子向彼此移动一步。 问能否通过若干次操作使得所有的棋子都在一个点上…

07 # 手写 find 方法

find 的使用 find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。 ele&#xff1a;表示数组中的每一个元素index&#xff1a;表示数据中元素的索引array&#xff1a;表示数组 <script>var arr [1, 3, 5, 7, 9];var result arr.find(fun…

【Edge】微软Edge每次启动自动导入Chrome收藏夹,无法取消“每次启动浏览器时导入浏览数据”功能的解决方法(202311)

写在前面 Edge现在也不管用户体验了吗? 这个BUG都快一个月了&#xff0c;还没见修复&#xff0c;从118.0.2088开始&#xff0c;我是在2023年10月份一次更新后发现的这个BUG&#xff0c;结果社区论坛什么信息都没有&#xff0c;英文也没收到。 Edge的BUG现象 不知道哪次Edge…

Chatgpt人工智能对话源码系统分享 带完整搭建教程

ChatGPT的开发基于大规模预训练模型技术。预训练模型是一种在大量文本数据上进行训练的模型&#xff0c;可以学习到各种语言模式和知识。在ChatGPT中&#xff0c;预训练模型被用于学习如何生成文本&#xff0c;并且可以用于各种不同的任务&#xff0c;如对话生成、问答、摘要等…

SpringBoot整合Kafka (二)

&#x1f4d1;前言 本文主要讲了SpringBoot整合Kafka文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ 上文链接&#xff1a;SpringBoot整合Kafka (一) &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是青衿&#x1f947; ☁️博客首页&#xff1a;CSDN主页…

IDEA 函数下边出现红色的波浪线,提示报错

Inferred annotations: Method makeOkResult: org.jetbrains.annotations.Contract("_, _, _, _ -> new") org.jetbrains.annotations.NotNull Parameter headers: org.jetbrains.annotations.NotNull 出现这个提示&#xff0c;我应该怎么处理这个函数&#xff1…

Lua中如何使用continue,goto continue(模拟C++ C#的continue)

Lua中模拟goto continue(模拟C C#的continue 介绍具体方法goto continuewhile模拟continue方法 总结 介绍 在C#或者C里面应该都见过continue&#xff0c;他的用法其实就是打断当前循环直接直接进入下次循环的&#xff0c;代码如下&#xff1a; for (int i 0; i < 10; i){i…

Angular项目升级最佳实践

1、查看第三方依赖库各版本的重大变化&#xff0c;确定待升级的Angular版本以及第三方库的版本。 2、在SourceTree中新建一个源码分支&#xff0c;用作项目升级。 3、安装新版node.js 4、打开https://update.angular.io/?v10.0-16.0&#xff0c;选择angular新旧版本&#x…

改进YOLOv5:结合ICCV2023|动态蛇形卷积,构建不规则目标识别网络

🔥🔥🔥 提升多尺度、不规则目标检测,创新提升 🔥🔥🔥 🔥🔥🔥 捕捉图像特征和处理复杂图像特征 🔥🔥🔥 👉👉👉: 本专栏包含大量的新设计的创新想法,包含详细的代码和说明,具备有效的创新组合,可以有效应用到改进创新当中 👉👉👉: �…

查看apk签名

cmd 命令&#xff1a; keytool -v -list -keystore "E:\xxx\release.jks"

kubernetes集群编排——k8s存储(configmap,secrets)

configmap 字面值创建 kubectl create configmap my-config --from-literalkey1config1 --from-literalkey2config2kubectl get cmkubectl describe cm my-config 通过文件创建 kubectl create configmap my-config-2 --from-file/etc/resolv.confkubectl describe cm my-confi…

深入剖析React Hooks中的 useCallback

前言 自 React 16.8 版本引入 Hooks 以来&#xff0c;useCallback 成为了前端开发者们越来越青睐的一个功能。useCallback 可以有效优化组件性能&#xff0c;尤其在处理函数式组件中的状态更新时。本文将详细介绍 useCallback 的用法及其注意事项。 1. useCallback 简介 use…

Unreal UnLua + Lua Protobuf

Unreal UnLua Lua Protobuf https://protobuf.dev/ protobuf wire format&#xff1a;pb 编译到底层的数据协议 https://github.com/starwing/lua-protobuf/blob/master/README.zh.md buffer 处理 lua string 可以当 buffer 用&#xff0c;# len 不会遇到 0 截断&#xf…

算法leetcode|85. 最大矩形(rust重拳出击)

文章目录 85. 最大矩形&#xff1a;样例 1&#xff1a;样例 2&#xff1a;样例 3&#xff1a;样例 4&#xff1a;样例 5&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 85. 最…

Python算法例8 将整数A转换为B

1. 问题描述 给定整数A和B&#xff0c;求出将整数A转换为B&#xff0c;需要改变bit的位数。 2. 问题示例 把31转换为14&#xff0c;需要改变2个bit位&#xff0c;即&#xff1a;&#xff08;31&#xff09;10&#xff08;11111&#xff09;2&#xff0c;&#xff08;14&…

c语言的内存使用

#include <stdio.h> #include <stdlib.h> typedef struct info{int a;char b; }Info, *INFO;int main(){INFO ptr (INFO)malloc(sizeof(Info) *3);ptr[0].a 100;ptr[1].b c;printf("[%c]\n", ptr[1].b);free(ptr)return 0; } 定义一个结构体Info&…

CAN 协议常见面试题总结

0.讲一下CAN通讯的过程 第一段&#xff1a;需要发送的通讯设备&#xff0c;先发送一个显性电平0&#xff0c;告诉其他通讯设备&#xff0c;需要开始通讯。 第二段&#xff1a;就是发送仲裁段&#xff0c;其中包括ID帧和数据帧类型&#xff0c;告诉其他通讯设备&#xff0c;需…

alipay sofa-ark-1.1.5 各种类加载器 优先级

. 各种类加载器 & 优先级 /*** <pre>* 类加载器的使用优先级&#xff08;由高到底&#xff09;* 0、JDKDelegateClassLoader* 1、ContainerClassLoader* 2、hook级别类{前置}加载器* 3、PluginClassLoader* 4、B…

uniapp分包

以下是一个完整的 Uniapp 分包示例&#xff0c;代码分布在不同的文件夹中&#xff0c;其中包含了两个子包 sub1 和 sub2&#xff0c;以及一个主包 main。 在项目根目录下创建 pages 文件夹&#xff0c;并在其中创建各个页面的文件夹。 在每个页面文件夹中创建对应的 vue 文件和…