C语言可变长的参数列表
C语言可创建接收参数个数不确定的函数。如常用的标准库函数printf就是一个接收参数个数可变的函数。函数printf至少要接收一个字符串作为它的第一个实参。但事实上,printf还能够接收任意数目的其他实参。printf的函数原型是:
int printf(const char *format, …);
其中的省略号(…)表示这个函数可以接收可变数目的各种类型的实参。
需要注意:这个省略号必须放在形参列表的末尾。
可变参数头文件<stdarg.h>
中的宏和定义,为创建一个可变长参数列表的函数提供了必须的功能。
stdarg.h可变长参数列表类型和宏:
标识符 | 说明 |
---|---|
va_list | 该类型适合于保存宏va_start、va_arg、和va_end所需的信息。为了访问到一个可变长参数列表中的参数,必须定义一个类型为va_list的对象 |
va_start | 在一个可变长参数列表中的参数被访问前,先调用这个宏。这个宏将初始化用va_list声明的对象,以供va_arg和va_end使用 |
va_arg | 这个宏展开成一个表示可变长参数列表中下一个参数的值的表达式,值的类型由宏的第二个参数决定。每次对va_arg的调用都要修改用va_list声明的对象,以使这个对象指向列表中的下一个实参 |
va_end | 当一个函数的可变长实参列表是通过宏va_start来引用时,宏va_end可用于从这样的函数中正常返回 |
C11增加了宏va_copy,这个宏接受两个va_list并将第二个参数复制给第一个参数。这就允许通过可变长参数列表的多次传递不用每次都从头开始。
下面演示一个接收可变个数实参的函数average。函数average的第一个实参总是待计算平均值的数据的个数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include<stdio.h> #include<stdarg.h> double average( int i, ...) { double total = 0; va_list ap; va_start (ap, i); for ( int j = 1; j <= i; ++j) { total += va_arg (ap, double ); } va_end (ap); return total / i; } int main() { double w = 37.5; double x = 22.5; double y = 1.7; double z = 10.2; printf ( "w = %.lf\nx = %.lf\ny = %.lf\nz = %.lf\n" , w, x, y, z); printf ( "w和x的平均数:%.3f\nw、x和y的平均数:%.3f\nw、x和y和z的平均数:%.3f\n" , average(2, w, x), average(3, w, x, y), average(4, w, x, y, z)); } |
除了宏va_copy,函数average使用了头文件<stdarg.h>
中所有的定义和宏。
上面的代码中,va_list类型的对象ap被宏va_start、va_arg和va_end用来处理函数average的可变长参数列表。
函数首先调用宏va_start来初始化对象ap,为宏va_arg和va_end使用ap做准备。
宏va_start接收两个实参——对象ap和参数列表中在省略号前的最右边的标识符,在本例中是i,宏va_start在这里使用i来判断可变长实参列表从哪里开始。
然后函数average反复地将可变长参数列表中的参数加到变量total上。通过宏va_arge,参数列表中的数据不断地被提取出来加到变量total上。
宏va_arg接收两个实参——对象ap和期望在实参列表中出现的数据的类型,在本例中是double。宏va_arg返回的是实参的值。
函数average用对象ap作为一个实参来调用宏va_end以实现从函数average正常返回到函数main中。
像printf和scanf这样带可变参数列表的函数是如何知道每个va_arg宏使用的是何种类型的数据呢?
在程序指向过程中,通过扫描格式控制字符串中的格式转换说明符来确定下一个将要出来的实参是何种类型。