使用 va_args 而不传递 num(如 printf)

Using va_args without passing num (like printf)

以下是我可以想出的将可变长度参数传递给函数的最基本示例:

int printme(int num, ...)
{
    va_list ap;
    va_start(ap, num);
    for (int i = 0; i < num; ++i) {
        char *arg = va_arg(ap, char *);
        printf("%d. %s\n", i + 1, arg);
    }
    va_end(ap);
    return 1;
}

int main(void)
{
    printme(2, "X", "YY");
}

但是,请注意我将长度作为第一个参数(或任何参数)传递。是否可以将这些 va_ 宏与类似 printf 的函数一起使用?例如,我可以做这样的事情吗(不传递参数的数量?

print2("Hello something %s %s", "Arg 1", "Arg 2");
print2("Hello something %s %s %s", "Arg 1", "Arg 2", "Arg 3");

如果是这样,接收它的函数是什么样的?如果不可能,那么 printf 如何实施呢?

这是一个从解析的字符串中检测参数数量的基本示例。它没有考虑任何特殊情况,也没有对 formatting/parsing 字符串做任何事情,但显示了一个基本的工作程序来说明如何从字符串中解析计数:

int printmy(char * format, ...)
{
    // num args
    int num = 0;
    for(char idx=0, c; c=format[idx]; idx++) {
        // ignore escape char + double-percent
        if (c=='\' || (c=='%' && format[idx+1]=='%'))
            idx++;
        else if (c=='%')
            num++;
    }

    // print variable args
    va_list ap;
    va_start(ap, format);   // need to give it the last argument before the "..."
    for (int i=0; i < num; ++i) {
        char* arg = va_arg(ap, char*);
        printf("%d. %s\n", i+1, arg);
    }
    va_end(ap);
    
    // return num args received
    return num;
}

int main(void)
{
    int num_args = printmy("Hello something \% \% %% %s %s %s", "Arg 1", "Arg 2", "Arg 3");    
    printf("Num Args Parsed: %d\n", num_args);
}

我们得到:

1. Arg 1
2. Arg 2
3. Arg 3
4.
Num Args Parsed: 4

如果你只想传递字符串,可以很容易地通过编写一个函数来解析第一个参数来完成,就像我在这里写的 countargs(char*) 一样。它returns参数个数:

#include <stdarg.h>
#include <stdio.h>
int printme(char* fmt, ...)
{
    va_list ap;
    int num = countargs(fmt);
    va_start(ap, num);
    for (int i=0; i < num; ++i) {
        char* arg = va_arg(ap, char*);
        printf("%d. %s\n", i+1, arg);
    }
    va_end(ap);
    return 1;
}

int countargs(char* fmt)
{
    int i, num = 0;
    if(strlen(fmt) < 2) 
    { 
        return 0;
    }
    for(i = 0; fmt[i+1] != '[=10=]'; i++)
    {
        if (fmt[i] == '%' && fmt[i+1] == 's')
        {
            num++;
        }
    }
    return num;
}

int main(void)
{
    printme("%s%s", "Stack", "Overflow");
}

您可以使用宏处理器来计算参数。

#include <stdarg.h>
#include <stdio.h>

int printme(int num, ...)
{
    va_list ap;
    va_start(ap, num);
    for (int i=0; i < num; ++i) {
        char* arg = va_arg(ap, char*);
        printf("%d. %s\n", i+1, arg);
    }
    va_end(ap);
    return 1;
}

#define TENTH(p1,p2,p3,p4,p5,p6,p7,p8,p9,p10,...) p10
#define NARGS(...) TENTH(__VA_ARGS__,9,8,7,6,5,4,3,2,1,0)
#define printme(...) printme(NARGS(__VA_ARGS__), __VA_ARGS__)

int main(void)
{
    printme("X", "YY");
}

工作原理:

TENTH只是returns第十参数 示例:

TENTH(a,b,c,d,e,f,g,h,i,j,k)

扩展为 j

可用于获取多个参数:

                               | tenth argument
                               v
TENTH("X", "XY", 9,8,7,6,5,4,3,2,1,0)

展开为2因为2TENTH的第十个参数。

同样,

                                    | tenth argument
                                    v
TENTH("A", "B", "C", "D", 9,8,7,6,5,4,3,2,1,0)

扩展为 4

NARGS 将其参数 放在 序列之前 9,8,... 移动序列以获得参数数量。

我已经使用 printme 来避免污染 namespace.Whenever 一个宏是 expand is get disabled 来避免无限递归。它不会进一步扩展成为对原始 printme() 的调用。第一个参数 num 是用 NARGS 宏计算的。所有剩余的参数都在 num 之后传递。

还有一些缺点:

  • 最多可处理十个参数,但可以轻松解除此限制
  • 需要至少一个参数(可以解决,但很复杂)