在写一个函数时,经常会有实现一个参数未知或不是常数的函数的需求。printf
就是这样的一个函数,在 Section 9.11中有详细描述。接下来的例子将想你展示如何声明一个这样的函数。
int f(int, ... ) {
.
.
.
}
int g() {
f(1,2,3);
}
//Example 9.5
为了得到被调用的函数的参数, <stdarg.h>
头文件中函数的声明必须要被包括。这就引入了一个新类型va_list
和三个对这个类型的对象的函数,它们是va_start
, va_arg
, 和va_end
。
在获取一个变量参数列表之前,va_start
一定要被调用,它被定义成
#include <stdarg.h>
void va_start(va_list ap, parmN);
va_start
宏初始了ap
为 接下来的函数va_arg
和va_end
所用。va_start
的第二个参数parmN
是作为标识符命名了函数定义(在...前面那个)中变量参数表中最右边参数。标识符parmN
不能被声明成register
的存储类或作为一个函数或数组类型。
一旦初始化了,提供的这些参数就可以在接下来被va_arg
宏所使用。这很奇怪,因为返回的类型是被宏的一个参数所定义的。注意这不可能由一个真正的函数去实现,而只能是一个宏。它被定义成
#include <stdarg.h>
type va_arg(va_list ap, type);
每次对这个宏的调用都会提取参数列表中的下一个参数作为一个特定类型的值。va_list
参数必须是一个被va_start
初始化的参数。如果接下来的参数不是特定的那种类型,那么这成了一个未定义的行为。在这里要注意避免可能由于数值类型转换而导致的问题。用char
或者short
作为va_arg
的第二个参数总是错误的:这些类型总是会提升到signed int
或unsigned int
中的一个,以及float
会转换成double
。注意是执行时才定义是否对象声明为类型char
, unsigned char
, unsigned short
,无符号的那些位阈会提升至unsigned int
,还是复杂化va_arg
。这可能是一些预期以外的微妙的事情发生的地方;时间会说明一切。
当va_arg
被调用却没有更多的参数时,这种行为也是不被定义的。
这里的type
(类型)参数的类型名,必须可以简单地用添加一个*
来转换成一个指向一个对象的指针 。简单类型如char
就可以(因为char *
就是一个指向字符对象的指针),但是字符数组就不行(char []
不能用添加*
的方式转换成‘指向字符数组的指针’)。好在,数组还是很好处理的--只要记住数组名作为函数调用实参的时候本身就会转换成一个指针就可以了。对于一个'字符数组'参数类型而言,正确的type
将会是char *
。
当所有参数都被处理了的时候,va_end
函数应该被调用,这将会防止va_list
里的那些已经被用过的参数再次呈递上去。如果va_end
没有被使用,这种行为是未定义的。
在调用va_end
以后,整个参数列表可以用再调用va_start
的方法被重新遍历。va_end
函数被声明为
#include <stdarg.h>
void va_end(va list ap);
接下来的例子展示了va_start
,va_arg
, 和va_end
在实现一个返回最大整型参数的函数里的用法
#include <stdarg.h>
#include <stdio.h>
int maxof(int, ...) ;
void f(void);
main(){
f();
exit(EXIT_SUCCESS);
}
int maxof(int n_args, ...){
register int i;
int max, a;
va_list ap;
va_start(ap, n_args);
max = va_arg(ap, int);
for(i = 2; i <= n_args; i++) {
if((a = va_arg(ap, int)) > max)
max = a;
}
va_end(ap);
return max;
}
void f(void) {
int i = 5;
int j[256];
j[42] = 24;
printf("%d\n",maxof(3, i, j[42], 0));
}
//Example 9.6