在C中函式int printf(const char *format, ...)
就是用來輸出訊息到畫面上的,在這個函式的參數中有個比較特別的參數...
,它就是C語言用來表達可變參數,意即參數的數量不定。
#include<stdio.h>
#include<stdarg.h>
void printList(int head, ...){
va_list args;
va_start(args, head);
for (int i = head; i != -1; i = va_arg(args, int)){
printf("%d\n", i);
}
va_end(args);
}
int main(void){
printList(4, 5, 6, 7, 8, 9, -1);
}
4
5
6
7
8
9
上面的程式碼有幾個我們需要關注的點。
va_list
: 型態是指標,可能是void*
或char*
,依據不同編譯器,可能會有不同結果。va_start
: 是一個marco,樣式void va_start(va_list arg_ptr, last_arg)
。va_arg
: 是一個marco,樣式type va_arg(va_list arg_ptr, type)
。va_end
: 是一個marco,樣式void va_end(va_list arg_ptr)
。以上這些都是包含在C的<stdarg.h>
中。
在函式的參數以...
代替一般參數,之後再調用函式時就可以填入任意數量的參數。
arg_ptr
是一個變數,用來指向最後一個固定的參數last_arg
(即範例中的head
)。
va_start
用來抓取最後一位固定的參數,使arg_ptr
指向last_arg
的下一個參數,即第一個可變參數。
va_arg
則是用來抓取那些任意數量的參數,type
指的是傳入參數的資料型態,由於取值的方法跟地址偏移量有關,所以需要知道資料類型。
va_end
用來表示終止檢索那些傳入任意參數,實際操作只是使arg_ptr
指向0以免意外發生。
這裡有幾個重點
...
所包含的參數型態都要一樣)va_arg
抓參數時,沒法辨認有多少個參數,不知何時終止,需要程式設計師自己處理。int
unsigned int
double
,下面這些不能使用。va_copy
這裡提一個我覺得比較重要的一點,我們可能會有個疑惑,為啥C設計時要我們設計師自己處理參數的數量,不能在傳入時順便統計一下。
問題點就是va_arg
這個marco取得參數的設計,由於函式在堆疊區展開時參數的地址是連續的,va_arg
抓取參數時是由指標移動偏移量取得的,這樣就沒法得知我們傳入了多少參數。
我們較常處理這問題的方法:
在C的<stdarg.h>
中,還有個marco_INTSIZEOF(n)
跟上述的那些marco的運作和記憶體對齊有關,有興趣了解的可以去翻翻源碼。
我們在定義marco也可以像函式使用...
,表達可變參數。
#include<stdio.h>
// __VA_ARGS__是原本C用來表達可變參數的替代詞
#define feedback1(...) ( \
printf(__VA_ARGS__) \
)
// 如果我們不想用C定義的__VA_ARGS__
// 我們可以自己定義一個名稱,格式如下
#define feedback2(args...) ( \
printf(args) \
)
// 下面的例子相比上面多個##
// 如果我們沒有傳入參數給args
// 那麼按照define的功能,文字替代
// 文字替代後會產生printf(format, )
// 這樣因為有','所以編譯會有問題
// ##有個功能就是在沒有參數傳入的情況下
// 消掉多餘的',',使得結果變成printf(format)
#define my_print(format, args...) ( \
printf(format, ##args) \
)
int main(void) {
feedback1("feedback1: I'm hungry.\n");
feedback2("feedback2: I'm hungry.\n");
my_print("my_print: I'm hungry.\n");
my_print("my_print: num1 = %d, num2 = %d\n", 45, 54);
return 0;
}
feedback1: I'm hungry.
feedback2: I'm hungry.
my_print: I'm hungry.
my_print: num1 = 45, num2 = 54
feedback1
中__VA_AGRS__
是C自有的名稱,編譯器會用來換成可變參數。
但我們也可以自己定義名稱,格式如feedback2
的args...
。
當沒有額外參數傳入可能會產生多餘的,
,造成格式編譯不過。
##
可以在沒有參數的情況下,消掉多餘的,
。
marco的可變參數,沒辦法單獨讀取參數,意即我們沒辦法像函式va_arg
一次次抓取變數,所以相對於函式有時候並沒法簡單值觀的實現某些特定方法。