C/C++语言获取不定参数个数以及不定参数指定参数的方法

文章介绍了在C/C++中如何处理不定参数,特别是如何通过宏定义ARGC自动获取参数个数,从而避免传入参数个数与实际不符的问题。通过示例代码展示了宏的展开过程,并提出了在参数数量超过8个时的解决方案以及处理无参数情况的优化。最后提供了一个代码生成函数arg_generate用于创建不同上限的参数计数宏。
摘要由CSDN通过智能技术生成

场景

C/C++有很多场景需要用到不定参数,比如最常用的就是printf函数,后面可以按格式跟着若干个不定参数。不定参数在函数中形参用...来表示,然后在函数体用va_startva_argva_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_MAXARGC(...)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_MAXARGC(...)ARGS(x, ...)这三个即可。

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值