我们在C语言编程中会遇到一些参数个数可变的函数,例如printf() ,这个函数,它的定义是这样的:
int printf( const char* format, ...);
然而,变长参数的函数到底怎么实现呢?
要实现这样一个变长参数的函数需要用到数据结构va_list,和宏va_start,va_arg,va_end,这些都是定义在stdarg.h中的宏。
va_list是定义了一个保存函数参数的数据结构。
va_start宏用来初始化va_list变量,其第一个参数为va_list对象,第二个参数为可变参数的前一个参数,是一个固定参数
在初始化完成va_list变量后,即可使用va_arg宏
va_arg宏用来得到后边的可变参数,其第一个参数为va_list对象,第二个参数为返回参数的类型
va_end宏用来结束变量的获取,将其指针持有变量置0
下面是是va_list的具体实现实现(vc 2003):
#ifdef _M_ALPHA typedef struct { char *a0; /* pointer to first homed integer argument */ int offset; /* byte offset of next parameter */ } va_list; #else typedef char * va_list; #endif
可以看到va_list实际上是一个机器类型相关的宏,除了alpha机器以外,其他机器类型都被定义为一个char类型的指针变量,之所以定义为char *是因为可以用该变量逐地址也就是逐字节对参数进行遍历。
宏的实现如下,内容比较容易懂,就不介绍了,如下:#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) #define va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) ) #define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) #define va_end(ap) ( ap = (va_list)0 ) #ifdef __cplusplus #define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) ) #else #define _ADDRESSOF(v) ( &(v) ) #endif
#include <stdio.h> #include <stdarg.h> #include <string.h> void test(char src[], ...); int main() { char str[] = "content"; test("this is %d %s %c", 123, str, 'p'); return 0; } void test(char src[], ...) { int num=1; va_list args; va_start(args, src); int i = 1; int len = strlen(src); while(i<len) { if(src[i-1] == '%') { switch(src[i]) { case 'd': { int p = va_arg(args, int); printf("%d args : %d\n", num++, p); break; } case 'c': { //处理字符串的时候,va_arg宏会将参数自动提升为整型 int ch = va_arg(args, int); printf("%d args : %c\n", num++, (char)ch); break; } case 's': { char * pStr = va_arg(args, char *); printf("%d args : %s\n", num++, pStr); break; } } } i++; } va_end(args); }