可变参数列表:
在我们一般经常使用的函数中,函数列出了期望接受的参数,但函数原型只能显示固定的参数,那么,如何让一个函数在不同的时候接受不同数目的参数呢!使用可变参数列表就可实现,当一个函数事先不确定有多少个参数但是可以接受一个或多个参数,可以使得函数可以接受1个以上的任意多个参数。
可变参数列表是通过宏来实现的,这些宏定义在stdarg.h头文件中,在这个头文件中声明了 一个类型va_list和三个宏va_start、va_arg、va_end配合使用,访问参数的值。
可变参数函数的原型声明格式为:type VAFunction(type arg1, type arg2, … ); 参数可以分为两部分:个数确定的固定参数和个数可变的可选参数。函数至少需要一个固定参数,固定参数的声明和普通函数一样;可选参数由于个数不确定,声明时用"…"表示。固定参数和可选参数公同构成一个函数的参数列表。
举个例子理解:比方求指定个数的平均值
#include <stdio.h> #include <stdarg.h> int average(int n,...) //定义一个函数,实现可变参数 { va_list arg; //定义一个变量arg为va_list类型 int i = 0; int sum = 0; va_start(arg, n); for(i=0; i<n; i++) //循环获取参数 { sum += va_arg(arg, int); } return sum/n; va_end(arg); } int main() { int ret1 = 0; int ret2 = 0; ret1 = average(3, 1,2,3); ret2 = average(4, 2,3,3,5); printf("%d\n",ret1); printf("%d\n",ret2); return 0; }
程序结果
在vs中我们可以转到定义处查看各个类型和宏具体是怎样实现的
1、首先va_list arg;
#ifndef _VA_LIST_DEFINED #ifdef _M_CEE_PURE typedef System::ArgIterator va_list; #else typedef char * va_list; #endif /* _M_CEE_PURE */ #define _VA_LIST_DEFINED #endif 很明显va_list就是一个类型重命名;va_list实际上就是char*型,简言之va_list arg就是声明了一个字符型指针arg。 2、va_start(arg, n);
#elif defined(_M_IX86) #define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) ) #define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) #define _crt_va_end(ap) ( ap = (va_list)0 ) #elif defined(_M_IA64)
_INTSIZEOF(n)整个做的事情就是将n的长度化为int长度的整数倍 va_srart(ap,v)就是把上面的字符指针向后移动,跳过第一个参数n的地址。
3、va_arg(args, int)
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
va_arg(args, int) 就是循环获取到可变参数列表中的参数,args指向下一个参数地址,返回的则是当前参数地址。 4、va_end(arg);
#define _crt_va_end(ap) ( ap = (va_list)0 )当访问完毕最后一个参数时,用VA_END宏结束可变参数的获取。
可变参数列表的缺陷:
1、可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途 终止,这是可以的,但是,如果你想一开始就访问参数列表中间的参数,那 是不行的
2、参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用 va_start。
3、如果在va_arg中指定了错误的类型,结果无法预测,因为在使用时,char、short、float类型的值实际上都作为int或double类型的值传递给函数。