通常情况下,我们使用一个函数时总是已经知道这个函数需要传多少个参数进去。但是我们也不难发现,当我们在使用printf函数时,我们总是想传多少参数进去就传多少参数进去,从来没有考虑过这个printf函数到底能接受多少个参数。实际情况是,printf函数就是可以接受任意个参数,因为printf函数是使用了可变参数列表来编写的。
那么什么是可变参数列表呢?
通过将函数实现为可变参数的形式,可以使得函数可以接受1个以上的任意多个参数(不固定)。
首先,我们来看一下printf函数:printf(char *format,…)。如果函数的声明或者定义(参数列表)中出现了…则表示该函数是可变参数的函数。
看一个例子:
#include<stdio.h>
#include<stdarg.h>
int findMax(int n,...)
{
va_list arg;//相当于char *p
int i = 0,M = 0;
va_start(arg, n);//相当于char *p = &n
for(i=0; i<n; i++)
{
int m = va_arg(arg,int);
if(m > M)
{
M = m;
}
}
return M;
va_end(arg);//相当于p = NULL
}
int main()
{
int a = 1;
int b = 3;
int c = 6;
int max = findMax(3, a, b,c);
printf("max = %d\n", max);
system("pause");
return 0;
}
首先我们需要声明一个 va_list 类型的变量 arg ,它将用于访问我们参数列表的未确定部分。
然后我们调用va_start用于初始化arg变量,它的第一个参数是va_list的变量名,第2个参数是省略号前的最后一个有名字的参数。初始化过程把 arg 变量设置为指向可变参数部分的第一个参数。
使用 va_arg来访问我们的参数,这个宏接受两个参数:va_list变量和参数列表中下一个参数的类型。在这个例子中所有的可变参数都是整型。va_arg返回这个参数的值,并且使用va_arg指向下一个可变参数。
最后,当访问完最后一个参数时我们需要调用va_end。
可变参数的限制
注意:
-可变参数必须从头到尾逐个访问。如果你在访问了了几个可变参数之后想半途终止,这是可以的,但是,如果你想一开始就访问参数列表中间的参数,那是不行的。
参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用va_start。
这些宏是无法直接判断实际存在参数的数量。
这些宏无法判断每个参数的类型。
如果在va_arg中指定了了错误的类型,那么其后果是不可预测的。
之前我们提到过关于栈帧的相关知识,那么同样,我们这里的可变参数列表也可以以栈帧的视角去理解。因为一个函数的调用必然会形成栈帧结构,就会进行形参实例化,因此我们可以通过栈帧结构去提取可变参数列表当中的每一个参数。关于栈帧的相关细节可以查看我的另一篇博客。C语言·关于栈帧(点击查阅)