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 中使用…...

TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...

Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...
Python Einops库:深度学习中的张量操作革命
Einops(爱因斯坦操作库)就像给张量操作戴上了一副"语义眼镜"——让你用人类能理解的方式告诉计算机如何操作多维数组。这个基于爱因斯坦求和约定的库,用类似自然语言的表达式替代了晦涩的API调用,彻底改变了深度学习工程…...

Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...
Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
一个面向 Java 开发者的 Sring-Ai 示例工程项目,该项目是一个 Spring AI 快速入门的样例工程项目,旨在通过一些小的案例展示 Spring AI 框架的核心功能和使用方法。 项目采用模块化设计,每个模块都专注于特定的功能领域,便于学习和…...
Python竞赛环境搭建全攻略
Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型(算法、数据分析、机器学习等)不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...

Python训练营-Day26-函数专题1:函数定义与参数
题目1:计算圆的面积 任务: 编写一个名为 calculate_circle_area 的函数,该函数接收圆的半径 radius 作为参数,并返回圆的面积。圆的面积 π * radius (可以使用 math.pi 作为 π 的值)要求:函数接收一个位置参数 radi…...
DAY 26 函数专题1
函数定义与参数知识点回顾:1. 函数的定义2. 变量作用域:局部变量和全局变量3. 函数的参数类型:位置参数、默认参数、不定参数4. 传递参数的手段:关键词参数5 题目1:计算圆的面积 任务: 编写一…...