在C语言中,我们一般经常使用的参数都是固定的,例如strcmp,strcpy,这些函数都只能有两个参数。但是在一些特殊的函数,例如printf,scanf中,他们的参数是可以改变的,这就是可变参数的使用。
可变参数列表是通过宏实现的,宏定义在头文件stdarg中,这个头文件声明了一个类型va_list,三个宏va_start,va_arg,va_end.
可变参数原型的声明格式:type VAFunction(type arg1,type arg2,...).参数分为两个部分:个数确定的固定参数和个数可变的可选参数。因为可选参数的个数是不确定的,所以用...来表示。
注意:函数至少有一个固定参数。
我们先举个例子:
实现一个函数可以求任意个参数的平均值
#include<stdio.h>
#include<stdarg.h>
int average(int n,...)
{
va_list arg;
int i = 0;
int sum = 0;
va_start(arg,n);
for( i=0; i<n; i++ )
{
sum = sum + va_arg(arg,int);
}
return sum/n;
va_end(arg);
}
int main()
{
int avg1 = average(3, 5, 6, 4);
int avg2 = average(5, 1, 4, 6, 8, 11);
printf("avg1 = %d\n",avg1);
printf("avg2 = %d\n",avg2);
return 0;
}
运行结果:
我们先转到定义看看它们是怎么实现的。
1.va_list arg
可以看出来,va_list其实就是char*,将char* 类型重命名为va_liat.
2.va_start(arg,n)
这个可能看着不太明白,那就继续转到定义。
我们再将不认识的字符转到定义。
现在看着就很明白了,我们将va_start这个宏替换一下。
arg = (va_list)&n+INTSIZEOF(n)
arg = (char*)&n+4
va_start(ap,v)就是把字符指针向后移动,跳过了第一个参数。
arg指向未知参数列表的第一个参数。
3.va_arg(arg,int)
现在,将va_arg这个宏替换一下。
*(int *)((arg = agr + 4) - 4)
这个表达式的作用是,arg=arg+4使arg指向下一个参数,再减去4,保留下来的就是上一个参数的地址。
4.va_end(arg)
将va_end替换一下,这个就比较简单。
arg = (char*)0
其实就是把va_end置成空指针,即va_end=NULL.
好了,我们现在总结一下:
· 声明一个va_list类型的变量arg,它用于访问参数列表的未确定部分。
· 这个变量是调用va_start来初始化的。它的第一个参数是va_list的变量名,第二个参数是省略号前最后一个有名字的参数。初始化过程把arg变量设置为指向可变参数部分的第一个参数。
· 为了访问参数,需要使用va_arg,这个宏接受两个参数:va_list变量和参数列表中下一个参数的类型。在这个例子中的所有可变参数都是整型。va_arg返回这个参数的值,并使用va_arg指向下一个可变参数。
· 最后,当访问完毕最后一个可变参数之后,我们需要调用va_end。
可变参数的限制
注意:
· 可变参数必须从头到尾逐个访问。不能半途而废,也不能从中间开始访问。
· 参数列表中至少有一个命名参数。若一个命名参数都没有,就无法使用va_start。
· 这些宏无法直接判断实际存在参数的数量。
· 这些宏无法判断每个参数的类型。
· 如果在va_arg中指定了错误的类型,那么其后果是不可预测的。