回 帖 发 新 帖 刷新版面

主题:如何动态调用那些变长参数的函数

举个例子:

printf大家都知道该怎么用,现在我想写一个myprintf函数,这个函数将自己的参数
完全传递给printf,

形如
void myprintf(char *format,...)
{
     printf(format,...);
}


请问各位,这个printf(format,...);该怎么写,才能让myprintf接收到的变长参数,原封不动的传递给printf.

谢谢了。

回复列表 (共9个回复)

沙发

这玩意很复杂,参考stdarg.h里面的有关内容
参考文献http://bigwhite.blogbus.com/logs/20468193.html

板凳

楼上的那篇文章讲得比较多(既讲了分析过程,又讲了C标准),但实际上也可以不了解那么多的。
只需要知道<stdarg.h>里面的四个东西,就足够了。这四个东西分别是:va_list, va_start, va_end, va_arg。

(1) va_list,这是一种数据类型,就像int那样。你可以定义va_list类型的变量。
    一个int类型的变量表示一个整数,一个double类型的变量表示一个浮点数,一个va_list类型的变量表示一个“指向参数的指针”,注意我这里说“指针”,但是并不需要“*”号。
(2) va_start,用于对一个va_list类型的变量进行初始化,让它指向你指定的参数的下一个参数。在使用一个va_list类型的变量之前,应该使用va_start来进行初始化。
(3) va_end,用于释放一个va_list类型的变量。当一个va_list类型的变量不再需要使用时,应该使用va_end来释放它。
(4) va_arg,用于取得va_list类型的变量所指向的参数,并把va_list指向下一个参数。


下面这段代码楼主可以结合理解:
[code=c]
void test_print(const char* fmt, ...)
{
    const char* p;
    va_list ap;            // 定义一个va_list类型的变量,变量的名字叫ap

    va_start(ap, fmt);    // 初始化变量ap,让它指向参数fmt的下一个参数。
    for (p = fmt; *p != '\0'; ++p)
    {
        char c = *p;
        if (c == '%')
        {
            ++p;
            if (*p == '\0')
            {
                break;
            }

            switch (*p)
            {
            case 's':
                {
                    const char* data = va_arg(ap, const char*);    // 取出ap所指向的参数(这个参数是一个const char*)。然后让ap指向下一个参数
                    printf("%s", data);
                }
                break;
            case 'd':
                {
                    int data = va_arg(ap, int);    // 取出ap所指向的参数(这个参数是一个int)。然后让ap指向下一个参数
                    printf("%d", data);
                }
                break;
            case 'f':
                {
                    double data = va_arg(ap, double);    // 取出ap所指向的参数(这个参数是一个double)。然后让ap指向下一个参数
                    printf("%f", data);
                }
                break;
            case '%':
                putchar('%');
                break;
            default:
                putchar(*p);
                break;
            }
        }
        else
        {
            putchar(c);
        }
    }

    va_end(ap);        // 释放ap
}[/code]

测试时我调用“test_print("%s %d %f\n", "aa", 30, 5.2);”,得到正确的结果“aa 30 5.200000”。


另外,有一个函数叫做vprintf,它的作用跟printf类似,但接受va_list类型的参数。
因此,可以这样写:
[code=c]void test_print(const char* fmt, ...)
{
    const char* p;
    va_list ap;            // 定义一个va_list类型的变量,变量的名字叫ap
    va_start(ap, fmt);    // 初始化变量ap,让它指向参数fmt的下一个参数。
    vprintf(fmt, ap);    // 利用vprintf函数完成输出
    va_end(ap);            // 释放ap
}[/code]

3 楼

学习了……

4 楼

谢谢eastcowboy。

你举的test_print函数的例子,是如何创建一个变长参数的函数,我问的不是这个。。

下面你又告诉我vprintf这个函数,也谢谢你,不过我搬出printf是为了举例,而不是真的要用这个。


其实我的问题就是
我想写一个函数,调用printf函数

void myprintf(char *format,...)
{
     printf(format,...);
}

我自己的myprintf函数中的...,也就是变长的参数,怎么作为printf的...也就是printf的变长参数
传给printf.


谢谢。

5 楼

猫同学,我想问,你把所有的参数都传给printf了那你为什么还要写自己的myprintf呢……
变长函数的用法,根据stdarg.h,就是需要建立一个参数列表,然后逐一取参进行调用
也就是说,参数长度可变的情况下,你无法把这些变长的参数一次性传递给printf的,只能自己给格式字符串写语法分析,然后分别传递

6 楼

其实这个问题,我已经用其他变通的方法解决掉了,
只不过遇到点有趣的事情总喜欢研究下,

麻烦了。。。

7 楼

交流一下你的“变通方法”,我来兴趣了

8 楼

C99标准定义了一个新的宏:__VA_ARGS__。
用它或许可以部分解决问题。但您可以做的不是函数,而是宏。

[code=c]#define MY_PRINTF(FMT, ...) \
  do { \
    /* 先在这里做一些事情 */ \
    /* 然后,把所有的参数都仍给printf */ \
    printf(FMT, __VA_ARGS__); \
    /* 完成之后,还可以再做一些事情 */ \
  } while (0);[/code]

其中__VA_ARGS__表示所有的参数。它的唯一作用就是可以把所有的参数一股脑的全部仍给一个支持可变参数的函数,例如printf。

之所以用do-while(0)扩起,是为了避免宏展开的时候遇到各种括号不匹配的问题。如果因为使用while(0)得到编译器警告,那就关闭它。

就像注释中说的,可以在调用printf之前、之后分别加入一些代码,从而完成更加丰富的功能。如果需要加入的代码过多,可以再定义一些函数,然后在宏里面调用这些函数。

微软对C99标准不怎么买帐,不过这个__VA_ARGS__还是支持了。至少我用Visual C++ 2008 Express实验,是可以的。当然,VC6是不行了,那个时代,C99标准还没出来呢。

另外的方法,我能想到的就只有汇编了。以前用汇编弄过一个,但感觉用处并不大,现在已经找不到了。

9 楼

谢谢eastcowboy,我昨天想了很多方法,有用结构体模拟参数数据块的,
有用汇编的 ,不过都非常麻烦,要判断参数大小或类型,再依次处理

__VA_ARGS__,应该是最好的一种方法

我来回复

您尚未登录,请登录后再回复。点此登录或注册