建议15:尽量不要使用可变参数(1)
在某些情况下我们希望函数参数的个数可以根据实际需要来确定,所以C语言中就提供了一种长度不确定的参数,形如:“...”,C++(www.cppentry.com)语言也继承了这一语言特性。在采用ANSI标准形式时,参数个数可变的函数的原型是:
- type funcname(type para1, type para2, ...);
这种形式至少需要一个普通的形式参数,后面的省略号(...)不能省去,它是函数原型必不可少的一部分。典型的例子有大家熟悉的printf()、scanf()函数,如下所示的就是printf()的原型: - int printf( const char *format , ... );
除了参数format固定以外,其他参数的个数和类型是不确定的。在实际调用时可以有以下形式:- int year = 2011;
- char str[] = "Hello 2011";
- printf("This year is %d", year);
- printf("The greeting words are %s", str);
- printf("This year is %d ,and the greeting words are:%s", year, str);
也许这些已经为大家所熟知,但是可变参数的实现原理却是C语言中比较难理解的一部分。在标准C语言中定义了一个头文件,专门用来对付可变参数列表,其中,包含了一个va_list的typedef声明和一组宏定义va_start、va_arg、va_end,如下所示:
- // File: VC++(www.cppentry.com)2010中的stdarg.h
- #include <vadefs.h>
-
- #define va_start _crt_va_start
- #define va_arg _crt_va_arg
- #define va_end _crt_va_end
-
-
- // File: VC++(www.cppentry.com)2010中的vadefs.h
- #ifndef _VA_LIST_DEFINED
- typedef char * va_list;
- #define _VA_LIST_DEFINED
- #endif
-
- #ifdef __cplusplus
- #define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )
- #else
- #define _ADDRESSOF(v) ( &(v) )
- #endif
-
- #ifdefined(_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 )
定义_INTSIZEOF(n)是为了使系统内存对齐;va_start(ap, v)使ap指向第一个可变参数在堆栈中的地址,va_arg(ap,t)使ap指向下一个可变参数的堆栈地址,并用*取得该地址的内容;最后变参获取完毕,通过va_end(ap)让ap不再指向堆栈,如图1-3所示。
|
| 图1-3 可变参数存储示意图 |
由于将va_start、va_arg、va_end定义成了宏,可变参数的类型和个数在该函数中完全由程序代码控制,并不能智能地进行识别,所以导致编译器对可变参数的函数原型检查不够严格,难于查错,不利于写出高质量的代码。
参数个数可变具有很多的优点,为程序员带来了很多的方便,但是上面C风格的可变参数却存在着如下的缺点: