1 自定义可变参数的函数
C语言中的<stdarg.h>中提供了可变参数的函数。使用步骤如下:
- 在函数原型中使用省略号,且省略号必须是最后一个参数
- 在函数定义中创建一个va_list类型的变量
- 用宏va_start将该变量初始化为一个参数列表
- 用宏va_arg访问参数列表
- 用宏va_end完成清理工作
下面进行详细说明。
1 函数原型
需要注意的是函数原型必须具有一个参数列表:在C/C++中,任何使用变长参数声明的函数都必须至少有一个指定的参数(又称强制参数),即至少有一个参数的类型是已知的,而不能用三个点省略所有参数的指定,且已知的指定参数必须声明在函数最左端。其中,最右边的参量(省略号前)起着特殊作用:ANSI标准使用parmN表示该变量,传递给该参量的值将是省略号部分代表的参数个数。比如下面的定义:
void f1(int n, ...); // OK int f2(int n, const char *s, ...); // OK char f3(char c1, ..., char c2); // Error double f4(...); // Error
2 va_list
va_list类型代表一种数据对象,该数据对象用于存放参量列表中省略号部分代表的参量。比如:
double sum(int lim, ...) { va_list ap; // 声明用于存放参数的变量 .... }
在这个例子中,lim为参量parmN,由它来指定可变参数列表中的参数个数。
3 va_start
我们使用va_start宏把参数列表复制到va_list变量中。va_start( )有两个参数:va_list类型的变量和参量ParmN。比如:
va_start(ap, lim); // 把ap初始化为参数列表
4 va_arg
第一次调用va_arg( )时,它返回参数列表的第一项,下次调用时返回第二项,依此类推。该宏接受两个参数:一个va_list类型的变量和一个类型名,该类型为va_arg( )返回值的类型。实际的参数类型必须与说明的类型匹配,否则将出错。需要注意的是:在可变长参数中,应用的是"加宽"原则。也就是float类型被扩展成double;char, short被扩展成int。因此,如果你要取可变长参数列表中原来为float类型的参数,需要用va_arg(argp, double);对char和short类型的则用va_arg(argp, int)。
需要注意的是va_arg没有提供后退回先前参数的方法,所以C99提供了一个宏va_copy( ),该宏的参数为两个va_list类型的变量,作用是将第二个参数复制到第一个参数中。
5 va_end
该宏完成清理工作:比如释放动态分配的用于存放参数的内存。其参数为一个va_list类型的变量。
下面是一个完整的例子:
#include <stdio.h> #include <stdarg.h> double sum(int, ...); int main(void) { double s, t; s = sum(3, 1.1, 2.5, 13.3); t = sum(6, 1.1, 2.5, 13.3, 4.1, 5.1, 6.1); printf("s = %gn", s); printf("t = %gn", t); return 0; } double sum(int lim, ...) { va_list ap; // 声明用于存放参数的变量 double tot = 0; int i; va_start(ap, lim); // 把ap初始化为参数列表 for (i = 0; i < lim; i++) tot += va_arg(ap, double); // 访问参数列表中的每一个项目 va_end(ap); // 清理工作 return tot; }
2 可变参数是如何工作的
其实只要了解C语言的函数参数传递的机制,可变参数的工作原理就不难理解。我们知道函数调用过程中参数的传递是通过栈来实现的,而且是按照从右至左的顺序依次压栈的。也就是说,(函数调用时)当所有的参数都入栈以后,可变参数一定在栈底,而强制参数一定在栈顶。这样结合参数类型,我们就可以从栈顶依次找到每一个参数的位置,从而取到每一个参数,va_arg宏正是做了这样一件事(所以,传给该宏的参数类型一定要正确,该宏并不会做任何的隐式、显式的类型转换)。由此,我们也知道为何函数原型中一定要有一个强制参数了。
同时也需要注意到parmN的值一定不能大于可变参数的个数,否则将访问到栈以外的数据。而且并不是所有的变长参数函数都有一个类似parmN这样的说明参数个数的值的参数,但是为了安全起见,最好能有这样一个参数。如果没有,也必须有一定的方式使我们可以知道访问到了最后一个参数,比如将-1设为最后一个参数。因为va_arg宏并没有实现探测参数列表的结尾,这个需要我们在程序里面自己去实现。
评论已关闭