《OOC》笔记(3)——C语言变长参数va_list的用法
C语言中赫赫有名的printf函数,能够接受的参数数目不固定,这就是变长参数。C#里也有params这个关键字用来实现变长参数。
1 printf("Hello Mozart!");2 printf("Hello %s!", "Mozart");3 printf("%d: Hello %s!", 77, "Mozart");
用C实现一个能接受变长参数的函数
举例如下。
1 #include2 3 int add(const char * testString, int x, ...) 4 { 5 printf("%s\n", testString); 6 va_list list; 7 va_start(list, x); 8 int result = 0; 9 10 for(;;)11 {12 int p = va_arg(list, int);13 if(p == 0)14 break;15 result += p;16 }17 va_end(list); // cleanup , set 'lsit' to NULL18 return result;19 }20 21 /* error: ISO C requires a named argument before '...'22 void add2(...)23 {24 }25 */26 27 int main()28 {29 int result = add("test case 1: ", 1, 2, 3, 4, 5, 0);30 printf("%d\n", result);31 system("PAUSE");32 return 0;33 }34 35 /*36 This program print as follows:37 test case 1: 38 1539 */
编写使用变长参数的函数步骤如下。
- 首先,引用stdarg.h。
- 然后,在函数声明中用"..."表示这个函数能够使用变长参数。
注意,在"..."前面至少要有一个普通的参数。(可能非标准C不需要,不过我们还是保守一点最好)
- 那么如何使用这些数目、类型都不确定的参数呢?
va_list类型的list变量可以遍历"..."中的参数。
用va_start()来初始化list变量。va_start()需要挨着"..."的左边那个参数名。(示例中的x)
va_arg()用于获取下一个参数值。这个参数值的类型你必须在编码时就能确定。
va_end()用于结束对list的遍历。之后你可以再次使用va_start()、va_arg()、va_end()来依次获取各个可变参数值。
注意事项
在add这个示例中,最后一个参数必须为0,add才能知道可变参数处理完毕。没有别的办法。也就是说,你不可能通过任何方式不借助外力就得知传进来的可变参数到底有几个。
在printf("%d, %s, %c", 1, "11", '1');函数中,printf会分析格式化参数"%d, %s, %c",它看到3个格式化输出符号,所以就认为传入了3个可变参数。如果你传入的多了或者少了,程序就可能出错。编译器无法检测这个错误。
list变量可以作为参数传递给其它函数。(如vprintf("xxx", list);)
在传递可变参数时,整型会作为int或long传递,float型会作为double传递。
va_arg()的第二个参数(示例中的int)不应太复杂。(这话很含糊)
总之,C语言中使用变长参数不是什么好的编程实践。能避免尽量避免。
原理是什么?
typedef char* va_list#define va_start(ap,v)(ap=(va_list)&v+_INTSIZEOF(v))#define va_arg(ap,t)(*(t*)((ap +=_INTSIZEOF(t))-_INTSIZEOF(t)))#define va_end(ap) ( ap = (va_list)0)
va_list是在stdio.h中定义的类型。va_start、va_arg、va_end是三个宏定义。
va_start把((v的地址)+(v的长度))赋给list。根据函数调用时形参的内存布局,这样list就指向了第一个可变参数。(示例中的2)
每次调用va_arg都会获得当前参数值,并将ap指针指向下一个参数。
调用va_end会将ap重置为0。
所以这个可变参数的原理就是一个迭代器,它在函数栈的参数上移动以依次获取可变参数。