场景
C/C++有很多场景需要用到不定参数,比如最常用的就是printf
函数,后面可以按格式跟着若干个不定参数。不定参数在函数中形参用...
来表示,然后在函数体用va_start
、va_arg
、va_end
这三个宏定义方法来承接不定参数,但是在承接不定参数时候,要承接多少个
呢?这个通常的方法是给函数传不定参数时候也传入不定参数个数。例子如下:
int adds(int num, ...)
{
int sum = 0;
va_list args;
va_start(args, num);
while (num--) sum += va_arg(args, int);
va_end(args);
return sum;
}
int main(int argc, char *argv[])
{
printf("sum = %d\r\n", adds(3, 1,2,3));
return 0;
}
结果:
sum = 6
但是这里面,如果传入num
个数和实际传入不定参数个数不一样,那么就会得不到预期的结果了。那么有什么方法是可以获取不定参数个数来保证传入的num
和实际的不定参数个数一致的呢,这就是本文要介绍的获取不定参数的方法。
获取不定参数个数
先来看效果,在前面的基础上加多宏定义
int adds(int num, ...)
{
int sum = 0;
va_list args;
va_start(args, num);
while (num--) sum += va_arg(args, int);
va_end(args);
return sum;
}
#define add(...) adds(ARGC(__VA_ARGS__), __VA_ARGS__)
int main(int argc, char *argv[])
{
printf("sum = %d\r\n", add(1,2,3));
printf("sum = %d\r\n", add(1,2,3,4));
printf("sum = %d\r\n", add(1,2,3,4,5));
return 0;
}
结果
sum = 6
sum = 10
sum = 15
可以看到,通过宏定义,不需要再手动传入宏定义参数个数了,那么这个ARGC
宏定义写了啥,先附上简短的代码
#include <stdlib.h>
#ifndef ARG_MAX
#define __ARGS(X) (X)
#define __ARGC_N(_0,_1,_2,_3,_4,_5,_6,_7,N,...) N
#define __ARGC(...) __ARGS(__ARGC_N(__VA_ARGS__,8,7,6,5,4,3,2,1))
#define __ARG0(_0,...) _0
#define __ARG1(_0,_1,...) _1
#define __ARG2(_0,_1,_2,...) _2
#define __ARG3(_0,_1,_2,_3,...) _3
#define __ARG4(_0,_1,_2,_3,_4,...) _4
#define __ARG5(_0,_1,_2,_3,_4,_5,...) _5
#define __ARG6(_0,_1,_2,_3,_4,_5,_6,...) _6
#define __ARG7(_0,_1,_2,_3,_4,_5,_6,_7,...) _7
#define __VA0(...) __ARGS(__ARG0(__VA_ARGS__,0)+0)
#define __VA1(...) __ARGS(__ARG1(__VA_ARGS__,0,0))
#define __VA2(...) __ARGS(__ARG2(__VA_ARGS__,0,0,0))
#define __VA3(...) __ARGS(__ARG3(__VA_ARGS__,0,0,0,0))
#define __VA4(...) __ARGS(__ARG4(__VA_ARGS__,0,0,0,0,0))
#define __VA5(...) __ARGS(__ARG5(__VA_ARGS__,0,0,0,0,0,0))
#define __VA6(...) __ARGS(__ARG6(__VA_ARGS__,0,0,0,0,0,0,0))
#define __VA7(...) __ARGS(__ARG7(__VA_ARGS__,0,0,0,0,0,0,0,0))
#define ARG_MAX 8
#define ARGC(...) __ARGC(__VA_ARGS__)
#define ARGS(x, ...) __VA##x(__VA_ARGS__)
#endif
在使用上,直接关注ARG_MAX
、ARGC(...)
、ARGS(x, ...)
这三个即可。
那么ARGC(...)
是怎么做到获取到不定参数个数的呢,以传入三个参数的为例,依次展开。
ARGC(a, b, c) // 第一步展开
__ARGC(a, b, c) // 换了个名字
(__ARGC_N(__VA_ARGS__,8,7,6,5,4,3,2,1)) // 由__ARGS(X) (X)展开,多了用个括号包住
(__ARGC_N(a,b,c,8,7,6,5,4,3,2,1))
展开到这里,按位置对应参数,a,b,c参数分别对应到了_0,_1,_2的位置,当__ARGC_N宏定义取N号位置的参数时,就取到了3,也就是3个不定参数。
(a, b, c, 8, 7, 6, 5, 4, 3, 2, 1)
| | | | | | | | |
| | | | | | | | |
(_0,_1, _2, _3, _4, _5, _6, _7, N, ...)
也就是得到最终的展开为 ARGC(a, b, c) ===>>> (3)
同样比如传入4个不定参数
(a, b, c, d, 8, 7, 6, 5, 4, 3, 2, 1)
| | | | | | | | |
| | | | | | | | |
(_0,_1, _2, _3, _4, _5, _6, _7, N, ...)
得到最终的展开为 ARGC(a, b, c, d) ===>>> (4)
但是,看到这里估计都会发现这里里面存在一个问题,当不定参数个数大于8了,展开是怎么样子的呢?
修改不定参数的个数上限
接着前面的问题,当参数大于8了,比如9个,展开是怎么样的呢?
传入9个不定参数
(a, b, c, d, e, f, g, h, i, 8, 7, 6, 5, 4, 3, 2, 1)
| | | | | | | | |
| | | | | | | | |
(_0,_1, _2, _3, _4, _5, _6, _7, N, ...)
得到最终的展开为 ARGC(a, b, c, d, e, f, g, h, i) ===>>> (i)
很明显大于8的参数个数是获取到了i,而不是我们所期待的9,因为这里能看到宏展开后最大也就是只能8,同理,该怎么增大这个上限,应该都能猜到怎么增大上限了。
别着急!不用自己手动去写,多麻烦,代码的生成函数我已经写出来了(在文末),直接copy这个函数,传入max就可以输出相应代码了。
一般来说,函数的参数不宜太多,参数太多压栈会影响函数执行效率也会降低代码可读性,一般超过8个参数就得考虑重写函数了。(C89最多31个形参,C99最多127个形参,没有验证过),这里是获取参数个数上限124的参考代码。
代码链接
不定参数个数优化
宏定义展开直接获取N
的话会存在一个问题,不传入参数,也是返回不定参数个数1,也就是
ARGC() ===>>> (1)
这是为什么呢?宏定义展开一下
传入9个不定参数
( , 8, 7, 6, 5, 4, 3, 2, 1)
| | | | | | | | |
| | | | | | | | |
(_0,_1, _2, _3, _4, _5, _6, _7, N, ...)
得到最终的展开为 ARGC() ===>>> (1)
这里不传入参数,_0
也会占着空位置,也就是等同1个参数了,所以最终也是获得1。
那有什么解决办法吗?
#define __ARGC_N(_0,_1,_2,_3,_4,_5,_6,_7,N,...) N
修改宏定义,把直接获取N
换成N==1?(#_0)[0]!=0:N
,这个是什么意思,再展开一下就得到N被换成了1
ARGC() ===>>> (1==1?("")[0]!=0:1)
就是把第0个参数的名字转成字符,当N==1的时候,判断是不是空字符串,是空字符串就表明没传入参数,就返回0,否则就N了。(这里(#_0)
转成字符串需要浪费空间)
获取不定参数的指定参数
如同前面,当把宏定义获取N换成获取其他位置,就可以获取对应位置的参数了。
以获取1号位置参数为例,宏定义展开结果。
(a, b, c, 0, 0)
| | | | |
| | | | |
(_0,_1, ...) _1
也就是得到最终的展开为 __VA1(a, b, c) ===>>> (b)
再配合
#define ARGS(x, ...) __VA##x(__VA_ARGS__)
最终 ARGS(1, a,b,c) ===>>> (b)
其他位置的参数展开也同理。
代码生成函数
// max为参数个数的上限,allow_0为时候支持获取空参数个数
int arg_generate(int max, int allow_0)
{
int n, i;
if (max <= 1) return 0;
printf("#include <stdlib.h>\r\n");
printf("\r\n");
printf("#ifndef ARG_MAX\r\n");
printf("\r\n");
printf("#define __ARGS(X) (X)\r\n");
printf("\r\n");
printf("#define __ARGC_N(");
for (n = 0; n < max; n++)
{
printf("_%d,", n);
}
printf("N,...) %s\r\n", allow_0?"N==1?(#_0)[0]!=0:N":"N");
printf("\r\n");
printf("#define __ARGC(...) __ARGS(__ARGC_N(__VA_ARGS__");
for (n = 0; n < max; n++)
{
printf(",%d", max - n);
}
printf("))\r\n");
printf("\r\n");
for (n = 0; n < max; n++)
{
printf("#define __ARG%d(", n);
for (i = 0; i <= n; i++)
{
printf("_%d,", i);
}
printf("...) _%d\r\n", n);
}
printf("\r\n");
for (n = 0; n < max; n++)
{
printf("#define __VA%d(...) __ARGS(__ARG%d(__VA_ARGS__", n, n);
for (i = 0; i <= n; i++)
{
printf(",0");
}
printf(")%s)\r\n", n?"":"+0");
}
printf("\r\n");
printf("#define ARG_MAX %d\r\n", max);
printf("#define ARGC(...) __ARGC(__VA_ARGS__)\r\n");
printf("#define ARGS(x, ...) __VA##x(__VA_ARGS__)\r\n");
printf("\r\n");
printf("#endif\r\n");
return 1;
}
总结
用int arg_generate(int max, int allow_0)
函数生成代码,直接使用ARG_MAX
、ARGC(...)
、ARGS(x, ...)
这三个即可。