关于C#:传递可变数量的参数

Passing variable number of arguments around

假设我有一个C函数,它接受一个可变数量的参数:我如何调用另一个函数,它期望从内部获得可变数量的参数,并传递进入第一个函数的所有参数?

例子:

1
2
3
4
5
6
void format_string(char *fmt, ...);

void debug_print(int dbg_lvl, char *fmt, ...) {
    format_string(fmt, /* how do I pass all the arguments from '...'? */);
    fprintf(stdout, fmt);
 }


要传递椭圆,必须将它们转换为va_列表,并在第二个函数中使用该va_列表。明确地;

1
2
3
4
5
6
7
8
9
10
11
12
13
void format_string(char *fmt,va_list argptr, char *formatted_string);


void debug_print(int dbg_lvl, char *fmt, ...)
{    
 char formatted_string[MAX_FMT_SIZE];

 va_list argptr;
 va_start(argptr,fmt);
 format_string(fmt, argptr, formatted_string);
 va_end(argptr);
 fprintf(stdout,"%s",formatted_string);
}


如果不知道要传递多少参数,就无法调用printf,除非你想使用淘气的、不可移植的技巧。

通常使用的解决方案是始终提供vararg函数的替代形式,因此printfvprintf,它用va_list代替......版本只是围绕va_list版本的包装纸。


可变函数可能很危险。下面是一个更安全的技巧:

1
2
3
4
5
6
7
8
   void func(type* values) {
        while(*values) {
            x = *values++;
            /* do whatever with x */
        }
    }

func((type[]){val1,val2,val3,val4,0});


在华丽的C++ 0x中,你可以使用可变模板:

1
2
3
4
5
6
7
8
template <typename ... Ts>
void format_string(char *fmt, Ts ... ts) {}

template <typename ... Ts>
void debug_print(int dbg_lvl, char *fmt, Ts ... ts)
{
  format_string(fmt, ts...);
}


可以对函数调用使用内联程序集。(在这段代码中,我假设参数是字符)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void format_string(char *fmt, ...);
void debug_print(int dbg_level, int numOfArgs, char *fmt, ...)
    {
        va_list argumentsToPass;
        va_start(argumentsToPass, fmt);
        char *list = new char[numOfArgs];
        for(int n = 0; n < numOfArgs; n++)
            list[n] = va_arg(argumentsToPass, char);
        va_end(argumentsToPass);
        for(int n = numOfArgs - 1; n >= 0; n--)
        {
            char next;
            next = list[n];
            __asm push next;
        }
        __asm push fmt;
        __asm call format_string;
        fprintf(stdout, fmt);
    }


虽然您可以通过首先将格式化程序存储在本地缓冲区来解决传递格式化程序的问题,但这需要堆栈,有时可能是需要处理的问题。我试着跟在后面,好像效果不错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdarg.h>
#include <stdio.h>

void print(char const* fmt, ...)
{
    va_list arg;
    va_start(arg, fmt);
    vprintf(fmt, arg);
    va_end(arg);
}

void printFormatted(char const* fmt, va_list arg)
{
    vprintf(fmt, arg);
}

void showLog(int mdl, char const* type, ...)
{
    print("
MDL: %d, TYPE: %s"
, mdl, type);

    va_list arg;
    va_start(arg, type);
    char const* fmt = va_arg(arg, char const*);
    printFormatted(fmt, arg);
    va_end(arg);
}

int main()
{
    int x = 3, y = 6;
    showLog(1,"INF,","Value = %d, %d Looks Good! %s", x, y,"Infact Awesome!!");
    showLog(1,"ERR");
}

希望这有帮助。


您也可以尝试宏。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define NONE    0x00
#define DBG     0x1F
#define INFO    0x0F
#define ERR     0x07
#define EMR     0x03
#define CRIT    0x01

#define DEBUG_LEVEL ERR

#define WHERESTR"[FILE : %s, FUNC : %s, LINE : %d]:"
#define WHEREARG __FILE__,__func__,__LINE__
#define DEBUG(...)  fprintf(stderr, __VA_ARGS__)
#define DEBUG_PRINT(X, _fmt, ...)  if((DEBUG_LEVEL & X) == X) \
                                      DEBUG(WHERESTR _fmt, WHEREARG,__VA_ARGS__)


int main()
{
    int x=10;
    DEBUG_PRINT(DBG,"i am x %d
"
, x);
    return 0;
}

罗斯的溶液洗了一点。只有当所有参数都是指针时才有效。如果EDOCX1 6是空的(VisualStudioC++和GCC DO),语言实现必须支持先前逗号。

1
2
3
4
5
6
// pass number of arguments version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__}; _actualFunction(args+1,sizeof(args) / sizeof(*args) - 1);}


// NULL terminated array version
 #define callVardicMethodSafely(...) {value_t *args[] = {NULL, __VA_ARGS__, NULL}; _actualFunction(args+1);}

假设你有一个典型的变量函数。因为在variadic one ...之前至少需要一个参数,所以在用法中必须始终编写一个额外的参数。

或者你呢?

如果将变量函数包装在宏中,则不需要前面的参数。考虑这个例子:

1
2
#define LOGI(...)
    ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))

这显然要方便得多,因为您不必每次都指定初始参数。


我不确定这是否适用于所有的编译器,但迄今为止它对我有效。

1
2
3
4
5
6
7
8
9
10
11
12
void inner_func(int &i)
{
  va_list vars;
  va_start(vars, i);
  int j = va_arg(vars);
  va_end(vars); // Generally useless, but should be included.
}

void func(int i, ...)
{
  inner_func(i);
}

您可以添加…如果你想的话,可以放在里面,但你不需要它。这是因为va_start使用给定变量的地址作为起点。在本例中,我们给它一个对func()中变量的引用。所以它使用这个地址并在堆栈上读取之后的变量。内部函数func()从func()的堆栈地址读取。因此,只有当两个函数使用相同的堆栈段时,它才起作用。

如果将任何var作为起点,va_start和va_arg宏通常都可以工作。因此,如果需要,您可以将指针传递给其他函数,也可以使用这些函数。您可以很容易地生成自己的宏。所有的宏都是类型化内存地址。然而,让它们适用于所有编译器和调用约定是令人讨厌的。因此,使用编译器附带的那些通常比较容易。