在函数的原型中,列出了函数期望接受的参数,但原型只能显示固定数目的参数,那么,让一个函数在不同需求下接受不同的参数是不是可以呢?答案是肯定的,比如我们的printf函数就是一个参数可变的函数。下面以一个求取平均值程序为例,帮助我们理解可变参数列表
#define _CRT_SECURE_NO_DEPRECATE
#include <stdio.h>
#include <stdarg.h>
#include <Windows.h>
int average(int num, ...)//num代表第一个参数,...代表可变参数//
{
/*char*p = #定义一个指针,并指向第一个形参地址,即指针初始化
int i = 0;
int sum = 0;
while (i <= num)
{
sum += *(int *)(p + 4 * i);从第一个形参地址处开始取值,直到取出所有数并求和
i++;
}//
va_list arg;//功能类似定义一个char*p
int sum = 0;
int n = 0;
va_start(arg, num);/* 初始化,找到第一个形参地址*/
while (n--)
{
sum += va_arg(arg, int); /*功能类似sum += *(int *)(p + 4 * i);*/
}
va_end(arg);/*arg=NULL*/
return sum / num;/*求平均值*/
}
int main(int argc,char*argv[],char *env[])
{
int a = 2;
int b = 4;
int c = 6;
int avg1 = average(a, b, c);
int avg2 = average( a, b);
printf("avg1=%d\n", avg1);
printf("avg2=%d\n", avg2);
system("pause");
return 0;
}
可变参数列表是通过宏来实现的。这些宏类似于stdarg.h头文件,他是标准库的一部分。这个头文件声明了一个类型va_list和三个宏———va_start,va_end,va_arg,有了这个宏以后,我们可以声明一个类型为va_list的变量(va_list arg类似一个定义一个 char *p),与这几个宏配合使用来访问参数的值
函数声明了一个va_arg用来访问参数列表中未确定的部分,这个变量通过调用va_start来初始化,他的第一个参数是va_list变量的名字,初始化过程把va_arg变量设置为指向可变参数部分的第一个参数,
为了访问参数,我们需要,需要使用va_arg,这个宏接受两个参数,va_list变量和可变参数列表中下一个参数的类型(在本案中,参数都是整形。而在有些函数中,你需要通过前面的数据来判断下一个参数了的类型)va_arg返回这个参数的值,并是va_arg指向下一个可变参数,最后,当访问完最后一个可变参数后,需要调用va_end,使访问过程结束。
可变参数函数调用要满足三个基本条件
- 参数列表中至少有一个命名参数,如果第一个连一个命名参数都没有,你就无法使用va_start。只有知道第一个命名参数,才能找到函数形参创建的起始位置,便于后面参数的查找与提取。
- 参数的类型,只有知道参数的类型才能正确的找到第一个形参后面的形参,这些宏无法判断参数的类型
- 参数的个数,决定指针查找参数这个过程在哪停止,函数实参个数有限,所以查过程也是有终点的,这些宏无法判断参数的数量。
当这三个基本条件满足后,函数调用就可以进行了。这里需要注意的是可变参数形参是从右往左依次创建的,具体过程参照我的博客《深度理解函数》中有详细介绍。创建的结果就是最左边的参数地址最低。如下图a的地址最低,其次是b,c的地址最高,可以参照下图帮助理解
当指针找到第一个形参地址时并取数,(数据是连续存储的)往后查找取数时需要依次查找,由于参数类型是int型,所以指针依次要越过四字节才能找到下一个地址取数,直到找到最后一个参数地址取数,(参数运算略)对可变参数全部操作即结束