《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 #include <stdarg.h> 2 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 NULL 18 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 15 39 */
編寫使用變長參數的函數步驟如下。
-
首先,引用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。
所以這個可變參數的原理就是一個迭代器,它在函數棧的參數上移動以依次獲取可變參數。
