就举一个简单的例子来解答这个问题吧,具有一定C语言编程基础的读者,一定知道求两个数平均值的函数,实现过程很简单,我们只需要把两个参数传给函数,并用一个变量接收函数返回来的结果即可。
但是,我们都知道现实生活中,我们需要求平均值的情况有很多种,比如,当我想要求某个同学期末平均成绩,这个时候可能需要传的参数个数就不是两个了。
那么对于不同数量的参数,我们如何通过编写一个函数来实现对不同数量的参数进行接收并处理呢?这就要用到可变参数列表了。
可变参数列表的实现是依靠标准库中stdarg.h里面定义的宏来实现的。在stdarg.h头文件中声明了一个类型va_list和三个宏——va_start, va_arg和va_end。 通过声明va_list的变量,与这三个宏的配合使用,可以访问参数的值。
其中,va_list的定义为:typedef char * va_list,下面是可变参数列表用到的三个宏的定义:
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_end(ap) ( ap = (va_list)0 )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
其中用到的_INTSIZEOF宏定义如下:
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
依次对上面的宏进行分析:
(1)首先说一下_INTSIZEOF(n),怎么理解这里呢?首先,联系到前面的宏定义 ,我们应该知道这里的n就指的是可变参数的类型,假设在32位的程序中,当可变参数类型为char时,对于二进制数(1+4-1) & ~(4-1)的结果为4,同样的,在32位的程序中,如果可变参数类型是short(占2个字节),int(占4个字节),double(占8个字节), long double(占12个字节)时,它的结果分别是4,4,8, 12。所以_INTSIZEOF(n)的功能就是取整到sizeof(int)。怎么理解取整到n呢?比如n为4,1,2,3,4取4,5,6,7,8取8。
(2)va_start(ap,v)中 ap = (va_list)&v + _INTSIZEOF(v),其中va_list是一个char *类型,类比于学习过的内容,就是取出v的地址中的第一个字节,然后加上_INTSIZEOF(v),并把得到的结果赋给ap。
(3)va_arg(ap,t),从ap中取出类型为t的数据,并将指针相应后移。如va_arg(ap, int)就表示取出一个int数据并将指针向移四个字节。
va_end(ap)很简单,就是将ap指针置成空指针。
因此在函数的整个过程就是:先用va_start()得到变参的起始地址,再用va_arg()一个一个取值,最后再用va_end()将指针置为空,说明参数获取完毕,接下来就轮到解析可变参数了。
需要注意一点,在C和C++中无法确定传入的可变参数的个数,所以一般情况下,在使用可变参数列表进行传参时,需要先指定传可变参数的个数。下面的代码中在主函数对Avg函数传参时,第一个参数4就说明了,要传入的可变参数的数量为4。还有一点需要说明一下,printf是根据"%"的个数来确定输入参数的个数的。
#include
#include
float Avg(int n, ...)
{
va_list arg;
int i = 0;
float sum = 0;
va_start(arg, n);
for (i = 0; i < n; ++i)
{
sum += va_arg(arg, int);
}
va_end(arg);
return sum / n;
}
int main()
{
printf("%.2f", Avg(4, 23, 43, 23, 12));
return 0;
}
(1)可变参数必须从头开始按顺序访问,不可以一开始就访问参数列表中间的值;
(2)参数列表至少包含一个命名参数。如果一个命名参数都没有,无法使用va_start;
(3)va_start, va_arg, va_end宏是无法确定实际存在的参数数量以及参数的类型。