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博客
作者的话
- 感谢参考资料的作者/博主
- 作者:夜悊
- 版权所有,转载请注明出处,谢谢~
- 如果文章对你有帮助,请点个赞或加个粉丝吧,你的支持就是作者的动力~
- 文章在描述时有疑惑的地方,请留言,定会一一耐心讨论、解答
- 文章在认识上有错误的地方, 敬请批评指正
- 望读者们都能有所收获
相关文章:
C 和 C++ 可变参数介绍
文章目录 前言概念C 的可变参数参数列表 #va_list 4组宏 C 的可变参数参数列表 #va_list 4组宏初始化列表 initializer_list<> 类模板可变参数模板 总结参考资料作者的话 前言 C 和 C 可变参数介绍。 概念 可变(长)/不定(长ÿ…...
【Git】gui图形化界面的使用、ssh协议以及idea集成Git
目录 gui图形化界面的使用 介绍 特点 gui图形的使用 ssh协议 介绍 步骤及概念 ssh协议的使用 配置公钥 idea集成Git idea配置git IDEA安装gitee IDEA中登入Git 编辑 项目分享 克隆分享的项目 编辑 编辑 idea上传远程 gui图形化界面的使用 介绍 GUI(…...
C语言之文件操作(详解版)
不知不觉我们已经学到C语言的文件操作部分了,这部分内容其实很有意思,因为它可以直接把我们代码中的数据写入硬盘,而不是我们关掉这个程序,代码就没有了,让我们开始学习吧! 目录 1.为什么使用文件 2.什么…...
解决mac 下 docker-compose 不是命令
docker-compose docker: ‘compose’ is not a docker command #6569 解决方法: mkdir -p /usr/local/lib/docker ln -s /Applications/Docker.app/Contents/Resources/cli-plugins /usr/local/lib/docker/cli-plugins参考: https://github.com/docker/…...
test_sizeof
test_sizeof //结论: // sizeof(arrU8)得到的大小是u8类型数组的 **定义大小**,在 初始化的时候用 // strlen(arrU8)得到的大小是u8类型数组的 **实际大小**,在 复制的时候用 //sizeof((char*)arrU8),把一个u8 * 转成 char *&…...
100+ Windows运行命令大全,装B高手必备
操作电脑关闭、重启、注销、休眠的命令细则: 用法: shutdown [/i | /l | /s | /sg | /r | /g | /a | /p | /h | /e | /o] [/hybrid] [/soft] [/fw] [/f] [/m \\computer][/t xxx][/d [p|u:]xx:yy [/c "comment"]] 没有参数 显示帮助。这与键入 /? 是一样的。…...
iOS 设置图标和upload包时显示错误
右键-show in finder-AppIcon.appiconset-然后替换图片 然后遇到个问题 就是图片不能有alpha [Xcode]应用图标:ERROR ITMS-90717: “Invalid App Store Icon. The App Store Icon in the asset catalog in x… 具体操作:只需确保【AppIcon】图片集中不…...
软件工程的舞台上,《人月神话》的美学纷飞
前言: Hello大家好,我是Dream。 今天给大家分享一本书:《人月神话》——软件工程的经典之作。 《人月神话》是一本具有深远影响力的软件工程著作,无论是软件开发者、管理者还是学习软件工程的人士,都能从中获得宝贵的启…...
C现代方法(第19章)笔记——程序设计
文章目录 第19章 程序设计19.1 模块19.1.1 内聚性与耦合性19.1.2 模块的类型 19.2 信息隐藏19.2.1 栈模块 19.3 抽象数据类型19.3.1 封装19.3.2 不完整类型 19.4 栈抽象数据类型19.4.1 为栈抽象数据类型定义接口19.4.2 用定长数组实现栈抽象数据类型19.4.3 改变栈抽象数据类型中…...
Elasticsearch 作为 GenAI 缓存层
作者:JEFF VESTAL,BAHA AZARMI 探索如何将 Elasticsearch 集成为缓存层,通过降低 token 成本和响应时间来优化生成式 AI 性能,这已通过实际测试和实际实施进行了证明。 随着生成式人工智能 (GenAI) 不断革新从客户服务到数据分析…...
FPGA与STM32_FSMC总线通信实验
FPGA与STM32_FSMC总线通信实验 内部存储器IP核的参数设置创建IP核FPGA代码STM32标准库的程序 STM32F407 上自带 FSMC 控制器,通过 FSMC 总线的地址复用模式实现STM32 与 FPGA 之间的通信,FPGA 内部建立 RAM 块,FPGA 桥接 STM32 和 RAM 块&…...
maven配置自定义下载路径,以及阿里云下载镜像
1.配置文件 <?xml version"1.0" encoding"UTF-8"?> <settings xmlns"http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation"http://maven.apache.org…...
01.单一职责原则
单一职责原则 概述 简单来说就是一个类只描述一件事, 比如我们熟知的 userDao.java 只负责 用户域功能。如果userDao既操作user表又操作order表,这显然不合理。正确的做法是让orderDao.java去操作order表。 对类来说的,一个类应该只负责一项…...
RT-Thread上部署TinyMaix推理框架,使MCU赋予AI能力
概要 当谈到微控制器(MCU)和人工智能(AI)的结合,我们进入了一个激动人心的领域。传统上,AI应用程序需要大型计算机或云服务器的处理能力,但随着技术的发展,现在可以将AI嵌入到微控制器中。这为嵌入式系统、物联网设备、机器人和各种其他应用开启了新的可能性。 MCU A…...
设计模式 -- 策略模式(Strategy Pattern)
策略模式:一种行为型模式,这些设计模式特别关注对象之间的通信。在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。 介绍 意图:定义一系列的算…...
Spring Boot 集成 ElasticSearch
1 加入依赖 首先创建一个项目,在项目中加入 ES 相关依赖,具体依赖如下所示: <dependency><groupId>org.elasticsearch</groupId><artifactId>elasticsearch</artifactId><version>7.1.0</version&g…...
百度智能云正式上线Python SDK版本并全面开源!
文章目录 1. SDK的优势2. 千帆SDK:快速落地LLM应用3. 如何快速上手千帆SDK3.1 SDK快速启动3.2 SDK进阶指引3.3 通过Langchain接入千帆SDK 4. 开源社区 百度智能云千帆大模型平台再次升级!在原有API基础上,百度智能云正式上线Python SDK&#…...
LeetCode(3)删除有序数组中的重复项【数组/字符串】【简单】
目录 1.题目2.答案3.提交结果截图 链接: 26. 删除有序数组中的重复项 1.题目 给你一个 非严格递增排列 的数组 nums ,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保…...
前端视角中的微信登录
目录 引入 流程介绍 具体实现 引入 本文主要讲解网站应用中微信登录的具体流程是怎么样的,以及作为前端开发人员在这整个流程中的主要任务是什么。 如果想要实现微信登录的功能,需要开发人员到微信开放平台注册相应的账号,进行注册应用&am…...
Python 中使用 Selenium 隐式等待
selenium 包用于使用 Python 脚本进行自动化和测试。 我们可以使用它来访问网页中的各个元素并使用它们。 该包中有许多方法可用于根据不同属性检索元素。 加载页面时,会动态检索一些元素。 与其他元素相比,这些元素的加载速度可能不同。 Python 中使用…...
在软件开发中正确使用MySQL日期时间类型的深度解析
在日常软件开发场景中,时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志,到供应链系统的物流节点时间戳,时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库,其日期时间类型的…...
【网络安全产品大调研系列】2. 体验漏洞扫描
前言 2023 年漏洞扫描服务市场规模预计为 3.06(十亿美元)。漏洞扫描服务市场行业预计将从 2024 年的 3.48(十亿美元)增长到 2032 年的 9.54(十亿美元)。预测期内漏洞扫描服务市场 CAGR(增长率&…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
[10-3]软件I2C读写MPU6050 江协科技学习笔记(16个知识点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...
Java入门学习详细版(一)
大家好,Java 学习是一个系统学习的过程,核心原则就是“理论 实践 坚持”,并且需循序渐进,不可过于着急,本篇文章推出的这份详细入门学习资料将带大家从零基础开始,逐步掌握 Java 的核心概念和编程技能。 …...
自然语言处理——循环神经网络
自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元(GRU)长短期记忆神经网络(LSTM)…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
[大语言模型]在个人电脑上部署ollama 并进行管理,最后配置AI程序开发助手.
ollama官网: 下载 https://ollama.com/ 安装 查看可以使用的模型 https://ollama.com/search 例如 https://ollama.com/library/deepseek-r1/tags # deepseek-r1:7bollama pull deepseek-r1:7b改token数量为409622 16384 ollama命令说明 ollama serve #:…...
