【问题标题】:Using va_args without passing num (like printf)使用 va_args 而不传递 num(如 printf)
【发布时间】:2021-03-06 06:18:50
【问题描述】:

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

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-ish 函数一起使用?例如,我可以做这样的事情(不传递参数的数量吗?

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

如果是这样,接收它的函数会是什么样子?而如果不可能,那printf又是如何实现的呢?

【问题讨论】:

  • 我很确定printf 使用格式字符串来查找% 字符,然后根据它们判断要做什么。例如,如果在%(意思是%s)之后有一个s,它就知道你正在这里打印一个字符串,所以它假设你传递的下一个可变参数是一个字符串。
  • 当然可以。如果printf 可以,那么你也可以。只需解析格式说明符的第一个字符串 arg。就像您的第一个示例使用 num 一样,您可以访问字符串 arg 并对其进行解析。
  • @kaylum 哦,对了。所以它只是接收它,并通过解析字符串来检测数字以确定count 是什么。
  • @carl.hiass — 是的!
  • @carl.hiass 为了完整起见,另一种处理相同类型的可变数量参数的 C 方法是使用保留值来指示列表的结尾,例如 print3("hello", "arg 1", "arg 2", "arg 3", NULL);

标签: c variadic-functions


【解决方案1】:

如果您只想传递字符串,可以很容易地通过编写一个函数来解析第一个参数,例如我在这里写的countargs(char*)。它返回参数的数量:

#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] != '\0'; i++)
    {
        if (fmt[i] == '%' && fmt[i+1] == 's')
        {
            num++;
        }
    }
    return num;
}

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

【讨论】:

  • 不需要strlen() 测试,只需使用for(i = 0; fmt[i] != '\0'; i++),如果fmt[i] 不是空终止符,fmt[i+1] 是可访问的并且%s 的测试按预期工作.
【解决方案2】:

这是一个从已解析字符串中检测参数数量的基本示例。它没有考虑任何特殊情况,也没有对实际格式化/解析字符串做任何事情,而是展示了一个基本的工作程序来展示如何从字符串中解析计数:

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

【讨论】:

    【解决方案3】:

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

    #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 只返回第十个 参数 示例:

    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 来避免污染命名空间。每当宏展开时,都会禁用 以避免无限递归。它不会进一步扩展为对原始printme() 的调用。第一个参数num 使用 NARGS 宏计算。所有剩余的参数都在num 之后传递。

    还有一些缺点:

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

    【讨论】:

    • 哇,这读起来真令人困惑。你能解释一下宏是如何工作的吗?
    猜你喜欢
    • 1970-01-01
    • 2011-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多