在c语言中常见的printf和scanf函数就是比较典型的可变参数函数。先看看printf的原型:
int printf(char *fmt,...)
其中,省略号表示参数表中的数量和类型是可以改变的。
下面是《The c programming language》154页的例子:
#include <stdarg.h>
#include <stdio.h>
void minprintf(char* fmt, ...)
{
va_list ap;
char *, *sval;
int ival;
double val;
va_start_(ap, fmt);
//(1)
for (p = fmt; *p; ++p) {
if (*p != '%') {
putchar(*p);
continue;
}
switch (*++p) {
case 'd':
ival = va_arg(ap, int);
printf("%d", ival);
break;
case 'f':
dval = va_arg(ap, double);
printf("%f", dval);
break;
case 's':
for (sval = va_arg(ap, char*); *sval; sval++)
putchar(*sval);
break;
default:
putchar(*p);
break;
}
}
va_end(ap);
}
int main()
{
minprintf("the boy name: %s, arge: %d, weight: %f kg\n", "xiaoming", 15, 51.5);
}
上面的函数输出为:
the boy name: xiaoming, arg: 15, weight: 51.5
下面是几点注意和知识点:
1、形如:void foo(char* fmt,...)的函数是参数可以改变的函数。
2、va_list 宏类型用于声明一个变量,该变量将依次引用各个参数。 (如上面例子的ap就是可变参数指针)
3、va_start 宏讲ap初初始化为指向第一个无名参数的指针。使用ap之前,该宏必须被调用一次。参数必须至少包括一个有名参数,va_start将最后一个有名参数作为起点。
4、va_arg 宏将返回ap所指的参数,并将ap指向下一个参数。va_arg使用一个类型名字来决定返回的对象类型、指针移动的步长。
5、va_end 以完成一些必要的清理工作。
另外:va_copy 宏的作用是重新复制一份va_list 的变量。
在新的编译器中,是支持这个宏的,但是在2.95.4之前的编译器有可能会有以下的错误:
va_copy is c99 - anything before that, and its upto the compiler... as pointed out, gcc 2.95 dosnt have it,depends on your definition of absolutely ancient, but the gcc 2.95.4 that comes with freebsd 4.x does not support va_copy
这表明,编译器还没有支持这个宏。
在linux 2.6.18已经使用这个宏(或者更早的内核已经使用了)。说明这个宏应该是可以的。放心使用。
使用例子:
函数如下:
void testva_copy(char *fmt, va_list arg)
{
char *p, *sval;
int ival;
double dval;
va_list ap;
va_copy(ap, arg);
for (p = fmt; *p; ++p) {
if (*p != '%') {
putchar(*p);
continue;
}
switch (*++p) {
case 'd':
ival = va_arg(ap, int);
printf("%d", ival);
break;
case 'f':
dval = va_arg(ap, double);
printf("%f", dval);
break;
case 's':
for (sval = va_arg(ap, char*); *sval; sval++)
putchar(*sval);
break;
default:
putchar(*p);
break;
}
}
va_end(ap);
}
在最前面的函数minprintf中(1)处调用testva_copy函数,结果是一样的。说明是简单的复制了va_list 的对象。