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)
- 02_可变长参数的基础_哔哩哔哩_bilibili
- va_list原理及用法-CSDN博客
- C++ 实现可变参数的三个方法 - Ofnoname - 博客园 (cnblogs)
- 第 2 章 语言可用性的强化 现代 C++ 教程: 高速上手 C++ 11/14/17/20 - Modern C++ Tutorial: C++ 11/14/17/20 On the Fly (changkun.de)
- c++11之函数参数包展开 - mohist - 博客园 (cnblogs)
- (C++模板编程):折叠表达式、可变参表达式_c++模板折叠-CSDN博客
作者的话
- 感谢参考资料的作者/博主
- 作者:夜悊
- 版权所有,转载请注明出处,谢谢~
- 如果文章对你有帮助,请点个赞或加个粉丝吧,你的支持就是作者的动力~
- 文章在描述时有疑惑的地方,请留言,定会一一耐心讨论、解答
- 文章在认识上有错误的地方, 敬请批评指正
- 望读者们都能有所收获
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)
- 02_可变长参数的基础_哔哩哔哩_bilibili
- va_list原理及用法-CSDN博客
- C++ 实现可变参数的三个方法 - Ofnoname - 博客园 (cnblogs)
- 第 2 章 语言可用性的强化 现代 C++ 教程: 高速上手 C++ 11/14/17/20 - Modern C++ Tutorial: C++ 11/14/17/20 On the Fly (changkun.de)
- c++11之函数参数包展开 - mohist - 博客园 (cnblogs)
- (C++模板编程):折叠表达式、可变参表达式_c++模板折叠-CSDN博客
作者的话
- 感谢参考资料的作者/博主
- 作者:夜悊
- 版权所有,转载请注明出处,谢谢~
- 如果文章对你有帮助,请点个赞或加个粉丝吧,你的支持就是作者的动力~
- 文章在描述时有疑惑的地方,请留言,定会一一耐心讨论、解答
- 文章在认识上有错误的地方, 敬请批评指正
- 望读者们都能有所收获