C语言中可变形参个数的函数实现
【@.1 可变形参个数函数】
C语言中,即使使用void*作为形参,也只能起到扩展形参类型的作用,而如果想要改变形参的个数呢?如果是C++的话就会很方便的通过函数的多态来实现这一功能,但是在C语言中实现起来就比较麻烦了。但并不是不可以实现,而是一般C编程没多少人用而已。考虑一个求平均数的函数,形参随便传入任意多个int类型的数,返回他们的平均值,用C语言应该怎么实现?这就需要用到可变形参个数的函数来实现了。
Variable Argument Lists, 在C语言中由stdarg.h头文件中定义了几个宏可以来做到这个事请。函数的形参无外乎是将他们以及函数的返回地址压入栈中,在运行时从栈中取出这些参数值,所以我们需要做的就是将传入的参数,不管有多少个,一个个从栈中找出来。
【@.2 求任意多个数的平均值函数】
float ave(int count,...) //Need to specify the count of vars { va_list valist; float sum=0; int i; va_start(valist,count); for(i=0;i<count;i++) sum+=va_arg(valist, int); va_end(valist); return sum/count; }
以上设计一个函数求任意多个数的平均值,注意到形参列表的第二个为"..."三个英文句号。
首先va_list就是头文件<stdarg.h>中定义的一个类型,专门用于处理这类问题。宏va_start()的第一个参数我们本来应该在"..."中传入的形参,第二个count是形参的开始,也就是这个函数ave的第一个形参count。注意这第一个形参可以是任意类型,这里我们将它作为传入"..."的个数。之后使用一个for循环,用va_arg()宏将形参列表中的下一个,也就是从你调用ave()的第二个形参开始,一个个弹出。注意,这里并没用到i,也就是说,这个va_arg()用一次就从栈中弹出下一个形参,而且不会检查栈是否为空,所以调用的次数必须由程序员自己控制好。最后,在函数返回之前用va_end()将这个形参列表清空,最后返回值即可。
printf("average:%f\n",ave(5, 1,2,3,7,9));
使用时可以直接想上面一样printf打印出来就可以看到结果,非常方便。再次注意,第一个参数我们用做后面待求平均数的个数传入函数的。
好这里就出现问题了,我随便输入了多少个参数之后,还一定得输入这些参数的个数,太麻烦了,有没有一种检查机制能自动帮我判断输入参数的个数呢?答案是,没有。就像上面说到的那样,用va_arg()宏弹出形参栈的时候的次数必须由程序员自己保证,所以有多少参数这个统计工作得由程序员自己做。好在,我们可以采用一些技巧来帮助我们完成这一任务,比如下面代码:
float average(int count,...) //Use an array to be the vars { va_list valist; float sum=0; int i; int *array; va_start(valist,count); array=va_arg(valist, int*); for(i=0;i<count;i++) { sum+=array[i]; printf("No.%2d:\t%d\n",i+1,array[i]); } va_end(valist); return sum/count; } int main(void) { int array[] = {5,7,9,11,13,15,20,22,16,11,-4,2,19}; float result = average(sizeof(array)/sizeof(int),array); printf("Average: %f\n",result); //printf("average:%f\n",ave(5, 1,2,3,7,9)); return EXIT_SUCCESS; }
将想要求平均数的一组数放在int[]数组array里,使用时只需修改这个数组即可,参数的个数统计由sizeof函数就可以算出,比刚才方便多了。在统计函数里面采用va_arg时将形参作为一个int*弹出,再控制好大小一个个取出来即可。
【@.3 可变参数个数函数与C语言的多态】
C++语言的继承,封装,多态的特性完全可以结合C语言中的函数指针和结构体来完成。而可变参数个数的函数这种技巧也提供给我们用C的面向对象风格编程一条新的思路。比如这篇博客或者另一篇博客就提到了这一技巧,其中对象的实例化就用到了这里的可变参数函数技巧。
@.[FIN] @.date->Jan 4, 2013 @.author->apollius