C/C++提供了一些处理可变长参数/扩展参数包的宏、函数、模板,本文主要是记录下他们的使用方式
stdarg.h 头文件定义了一个变量类型 va_list 和三个宏,这三个宏可用于在参数个数未知(即参数个数可变)时获取函数中的参数。可变参数的函数通在参数列表的末尾是使用省略号(,...)定义的。很多C语言库都是使用这种方式来处理输入参数列表的。
下面直接通过代码来演示如何使用,代码功能为统计N个参数的累加和:
#include
#include
int stdarg_counter(int count, ...)
{
int sum = 0;
//这是一个适用于 va_start()、va_arg() 和 va_end() 这三个宏存储信息的类型
std::va_list args;
//这个宏初始化args变量,它与 va_arg 和 va_end 宏是一起使用的。
//第二个参数count是最后一个传递给函数的已知的固定参数,即省略号之前的参数。
va_start(args, count);
for (int i = 0; i < count; ++i) {
//这个宏检索函数参数列表中类型为 type 的下一个参数
sum += va_arg(args, int);
}
//这个宏允许使用了 va_start 宏的带有可变参数的函数返回。
//如果在从函数返回之前没有调用 va_end,则结果为未定义。
va_end(args);
return sum;
}
int main()
{
std::cout << "stdarg_counter:" << stdarg_counter(4, 1, 3, 5, 7) << std::endl;
system("pause");
return 0;
}
一般我们会组合使用多种参数类型,下面的代码功能为找键值对参数value对应的key值:
int stdarg_find(int count, const char * target, ...)
{
int key = -1, find = -1;
const char * value = nullptr;
std::va_list args;
va_start(args, target);
for (int i = 0; i < count; ++i) {
key = va_arg(args, int);
value = va_arg(args, const char *);
if (strcmp(target, value) == 0) {
find = key;
break;
}
}
va_end(args);
return find;
}
int main()
{
std::cout << "stdarg_find:" << stdarg_find(4, "gongjianbo",
12, "a", 1992, "gongjianbo", 5, "b") << std::endl;
system("pause");
return 0;
}
参考:https://zh.cppreference.com/w/cpp/utility/variadic/va_start
参考:https://www.runoob.com/cprogramming/c-standard-library-stdarg-h.html
C99 引入了对参数个数可变的函数式宏的支持。在宏 “原型” 的末尾加上符号 … (就像在参数可变的函数定义中), 宏定义中的伪宏 __VA_ARGS__ 就会在调用是 替换成可变参数。如:
#define debug(...) printf(__VA_ARGS__)
这个宏好像在打印日志的场景里用的多点,网上的例子也多是类似的
#include
#define debug(...) printf(__VA_ARGS__)
int main()
{
debug("%s %d \n","gong jian bo",1992);
system("pause");
return 0;
}
参考:https://blog.csdn.net/bat67/article/details/77542165
因为C中提供的可变长参数机制缺少长度和类型检查,得靠使用者自己去处理,存在安全问题。而C++11也提供了两种方式来支持可变长参数:std::initializer_list参数列表、模板参数包。
std::initializer_list
标准库中很多容器都使用了std::initializer_list来实现容器的列表初始化,使得初始化更方便:
(截图来自vector参考手册https://zh.cppreference.com/w/cpp/container/vector/vector)
下面也用他实现一个累加功能:
#include
#include
template
T list_counter(std::initializer_list args)
{
T temp = T();
for (const T &item : args) {
temp += item;
}
return temp;
}
int main()
{
std::cout << list_counter({ 1, 2, 3.0, 4.5 }) << std::endl;
// 1.5
system("pause");
return 0;
}
std::initializer_list还支持迭代器,此处略
参考:https://zh.cppreference.com/w/cpp/utility/initializer_list
参考:https://www.jianshu.com/p/e5b781275ba9
模板形参包是接受零或更多模板实参(非类型、类型或模板)的模板形参。函数模板形参包是接受0或多个函数实参的函数形参。至少有一个形参包的模板被称作变参模板。
可变参数模板和普通模板的语义是一样的,只是写法上稍有区别,声明可变参数模板时需要在typename或class后面带上省略号,形如:
template
void f(T... args);
(这个语法真的很挫,省略号位置都变了)
变长参数包无法如同一般参数在类或函数中使用,因此典型的手法是以递归的方法取出可用参数:
#include
//用来结束递归
void my_cout()
{
std::cout << "end" << std::endl;
}
template
void my_cout(T first,Types ... args)
{
//通过操作符 sizeof... 获取参数包中的参数个数
std::cout << sizeof...(args) <<" "<< first << std::endl;
//递归取值
my_cout(args...);
}
int main()
{
my_cout(1, true, "gongjianbo", 1992, "last");
system("pause");
return 0;
}
(参数包还有很多花样,我这里就不写了,详见参考文档。展开参数包稍显麻烦,而 C++17 的折叠表达式使得展开参数包变得容易,好吧,我也是为了学习折叠表达式才复习了下本文的内容)
参考:https://zh.cppreference.com/w/cpp/language/parameter_pack
参考:https://www.cnblogs.com/qicosmos/p/4325949.html
折叠表达式:https://zh.cppreference.com/w/cpp/language/fold
折叠表达式:https://blog.csdn.net/ding_yingzi/article/details/79973809