在函数的原型中列出了函数所期望的参数个数以及类型,但是原型只能显示固定的参数个数。那么函数能不能接受1个或者任意个不固定的参数呢?答案是肯定的,这就要用到C语言中的可变参数列表了。下面我们来看一个例子。
#include <stdio.h>
int Add(int x,int y)
{
return x+y;
}
int main()
{
int ret = 0;
ret = Add(2,3);
ret = Add(1,2,3);
ret = Add(1);
return 0;
}
运行时显示错误,提示Add函数参数太少、参数太多,这说明这种情况下函数只能接受固定个数的参数。那么我们就可以通过可变参数列表来实现求任意数目的参数的和。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdarg.h>
int Add(int n,...)
{
va_list arg;
int sum = 0;
int i = 0;
va_start(arg,n);
for(i=0;i<n;i++)
{
sum += va_arg(arg,int);
}
va_end(arg);
return sum;
}
int main()
{
int ret1 = Add(2,2,3);
int ret2 = Add(3,1,2,3);
int ret3 = Add(1,1);
printf("ret1=%d, ret2=%d, ret3=%d\n",ret1,ret2,ret3);
return 0;
}
运行结果
结果说明可变参数列表可以实现函数接受任意个数的参数。下来我就来简单介绍一下可变参数列表。
可变参数列表
C语言中的可变参数,通过将函数实现为可变参数的形式,可以使得函数可以接受1个以上的任意个参数(不固定)。
可变参数列表是通过宏来实现的,这些宏定义在头文件stdarg.h中。这个头文件定义了一个va_list类型,三个宏:va_start、va_arg、va_end。下面我们就来看一下这些宏是如何定义的。(VC6.0)
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
这个宏的作用是向上取整,使得内存对齐。当n为1,2,3,4时,该宏的结果为4,当n为5,6,7,8时,该宏的结果为8,依此类推。
我们可以通过求任意个数参数的平均值来看一下这些宏是如何使用的。
#include <stdio.h>
#include <stdarg.h>
int ave(int n,...)
{
int i = 0;
int sum = 0;
va_list arg;
va_start(arg,n);
for(i=0;i<n;i++)
{
sum += va_arg(arg,int);
}
return sum/n;
va_end(arg);
}
int main()
{
printf("%d\n",ave(3,2,3,4));
return 0;
}
运行结果
将char*类型重定义为va_list;va_start(arg,n)指向未知参数部分的第一个元素;va_arg(arg,int)给定类型查对应的数据,执行一次arg指向下一个元素的地址;va_end(arg)将arg赋为空指针。我们将上述程序中的va_用宏来代替,可以得到下面这个程序。
#include <stdio.h>
#include <stdarg.h>
int ave(int n,...)
{
int i = 0;
int sum = 0;
//va_list arg;
char *arg;
//va_start(arg,n);
( arg = (char*)&n + 4 );
for(i=0;i<n;i++)
{
//sum += va_arg(arg,int);
sum += ( *(int *)((arg += 4) - 4) );
}
return sum/n;
//va_end(arg);
arg = (char*)NULL;
}
int main()
{
printf("%d\n",ave(3,2,3,4));
return 0;
}
运行结果
由此可见,可变参数的实现过程是使用宏的封装,只要完成替换,我们就可以自己分析了。
结论
- 声明一个va_list类型的变量arg,它用于访问参数列表的未确定部分。
- 这个变量是调用va_start来初始化的,va_start有两个参数,它的第一个参数是va_list的变量名,第2个参数是省略号前最后一个有名字的参数。初始化过程把arg变量设置为指向可变参数部分的第一个参数。
- 使用va_arg来访问参数,这个宏有两个参数:va_list变量和参数列表中下一个参数的类型。这个宏返回这个参数的值,并指向下一个参数。
- 当访问完毕,我们需要使用va_end,将va_list变量赋为空指针。
可变参数的限制
- 可变参数必须从头到尾逐个访问。如果你在访问了几个参数后想半途终止,这是可以的,但是,如果你想一开始就访问参数列表中间的参数,那是不行的。
- 参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用va_list。
- 这些宏是无法直接判断实际存在的参数的数量。
这些宏无法判断每个参数的类型。
警告:如果你在va_arg中指定了错误的类型,那么其结果是不可预测的。这个错误是很容易发生的,因为va_arg无法正确识别作用于可变参数之上的缺省参数类型提升。所以在使用va_arg时要特别注意类型的传递。