如何在C语言中定义参数数目可变的函数
提前声明:由于我对C的理解还不够深刻,所以这个东西里面可能会有些常识性的错误,欢迎各位不吝指出。希望这个东西对大家有所帮助。昨天晚上偶然看到一篇文章,讲到了C语言可变数目参数的函数。其实很早之前我就注意到<stdio.h>中的几个函数的参数个数是可变的,但一直不知道怎么实现。看了那篇文章,稍微有了点思路。
首先,文章介绍了几个宏——va_start, va_end, va_arg。我查了一下C99,其7.15节介绍的就是Variable Arguments<stdarg.h>(我比较奇怪的是, C99在这一节中的介绍顺序, va_list->va_arg->va_copy->va_end->va_start, 呵呵). 简单说一下这几个va_开头的东西的含义吧.
(1) va_list: typedef char* va_list
va_start: 声明为 void va_start ( va_list ap, parmN );
顾名思义, va_start要在所有其它的va_开头的宏前面最先使用(除了用va_list定义变量外), va_start的作用就是初始化ap, 因为后面的va_copy, va_arg, va_end都要使用到ap. C99说的很明白, 由va_start和va_end构成一个scope, 所以在一对va_start和va_end之间不能再次使用va_start. parmN 就是"..."之前的最后一个参数. 例如, printf函数是这样定义的: printf(const char *format, ...); 那么在printf函数中的va_start使用之后, parmN 的值就等于*format.
(2) va_arg: 声明为 type va_arg (va_list ap, type);
va_arg的作用就是返回参数列表ap中的下一个具有type类型的参数, 每次调用va_arg都会修改ap的值, 所以你才能"连续不断"的获取下一个type类型的参数. ap就是前面使用va_start初始化的ap.
(3) va_copy: 声明为 void va_copy (va_list dest, va_list src);
其作用是将src中的内容复制到dest中, dest成为src的一个副本.
(4) va_end: 声明为 void va_end (va_list ap);
va_end与va_start构成了一个scope, va_end之后ap就无效了.
其实C99的这些规定已经足够我们使用了. 让人欣慰的是, GCC提供了更加便捷有效的方式来处理这些va_开头的宏. 在GCC的<ansidecl.h>中定义了三个宏: VA_OPEN, VA_FIXEDARG, VA_CLOSE. 在注释中这样说:
/*
...
VA_OPEN and VA_CLOSE are used *instead of* va_start and va_end. Immediately after VA_OPEN, put a sequence of VA_FIXEDARG calls corresponding to the list of fixed arguments. Then use va_arg normally to get the variable arguments, or pass your va_list object around. You do not declare the va_list yourself; VA_OPEN does it for you.
...
You can declare variables either before or after the VA_OPEN, VA_FIXEDARG sequence. Also, VA_OPEN and VA_CLOSE are the beginning and end of a block. They must appear at the same nesting level, and any variables declared after VA_OPEN go out of scope at VA_CLOSE. Unfortunately, with a K+R compiler, that includes the argument list. You can have multiple instances of VA_OPEN/VA_CLOSE pairs in a single function in case you need to traverse the
argument list more than once.
...
*/
也就是说, 如果我们使用VA_OPEN和VA_CLOSE那么连定义va_list的变量也省了. 我们可以看一下VA_OPEN, VA_CLOSE, VA_FIXEDARG的定义:
#define VA_OPEN(AP, VAR) { va_list AP; va_start(AP); { struct Qdmy
#define VA_CLOSE(AP) } va_end(AP); }
#define VA_FIXEDARG(AP, TYPE, NAME) TYPE NAME = va_arg(AP, TYPE)
我们可以根据需要来使用这些宏.
下面我们就来写一个例子, 编译环境DevCpp 4.9.9.2
程序代码:
#include <stdio.h> #include <stdlib.h> #include <ansidecl.h> #include <stdarg.h> #define va_list char* // 这个其实是GCC的<ansidecl.h>中的示例程序, 作用就是printf int vary_args PARAMS ((const char *format, ...)); // 这个是我简单写的一个函数, 作用是求最大值 int max_v PARAMS ((int nValue, ...)); int main(int argc, char *argv[]) { char str[] = "Hello, world. \n %s"; char str2[] = "World, hello.\n"; int nResult = vary_args(str, str2); printf("nResult = %d\n", nResult); int nMaxV = max_v(6, 5, 3, 4); printf("nMaxV = %d\n", nMaxV); system("pause"); return 0; } int vary_args VPARAMS ((const char *format, ...)) { int result = 0; VA_OPEN (ap, format); VA_FIXEDARG(ap, const char *, format); result = vfprintf(stdout, format, ap); /* 关于vfprintf和stdout这里先不作说明, 有兴趣的可以查阅相关资料 */ VA_CLOSE(ap); return result; } int max_v PARAMS ((int nValue, ...)) { int result = 0, nTemp = 0; VA_OPEN(ap, nValue); result = nValue; while (strcmp(ap, "")) { //VA_FIXEDARG(ap, int, nTemp); nTemp = va_arg(ap, int); if (nTemp > result) result = nTemp; } VA_CLOSE(ap); return result; }
[[italic] 本帖最后由 zbqf109 于 2007-12-31 01:45 编辑 [/italic]]