c語言允許定義引數數量可變的函式,這稱為可變引數函式(variadic function)。這種函式需要固定數量的強制引數,後面是數量可變的可選引數。
其中,強制引數必須至少乙個,可選引數數量可變,型別可變,可選引數的數量由強制引數的值決定,或由用來定義可選引數列表的特殊值決定。
其實我們早就接觸過可變引數函式了,c 語言中最常用的可變引數函式例子是 printf()和 scanf()。這兩個函式都有乙個強制引數,即格式化字串。格式化字串中的轉換修飾符決定了可選引數的數量和型別。(是吧,printf中可以有自定義個%d,沒毛病)
可變引數函式的引數列表的格式是,強制性引數在前,後面跟著乙個逗號和省略號(…),這個省略號代表可選引數。比如:int fun(int, …) (我隨便舉的例子啊)..
1、問題引入
我們先來思考這樣乙個問題,作為本節的引入:
如果我們要預先寫乙個可變引數的累加求和函式,其強制引數型別為int,用它來表示我們一共要傳入多少個可變引數。於是我們大概可以有這麼乙個框架:
double
getsum
(int numofpara,...)}
發現什麼問題沒有?
由於已經知道要傳入多少個可變引數,所以求和思路就是,for迴圈遍歷numofpara次,每次都把sum加上乙個可變引數。
思路很清晰,沒毛病。但是,問題來了。由於是可變引數,我們無法提前得知可變參量的名字,也就沒法訪問這些可變引數。(你引數列表裡都是一串省略號了,你怎麼可能提前知道變數名,所以自然而然也無法表示、無法訪問這些變數了)
2、實現思路
為了解決上述問題,c語言規定:
當編寫可變引數函式時,必須用 va_list 型別定義引數指標,以獲取可選引數。可變引數函式要獲取可選引數時,必須通過乙個型別為 va_list 的物件來進行訪問,它包含了引數資訊。
這種型別的物件也稱為引數指標(argument pointer),它包含了棧中至少乙個引數的位置。可以使用這個引數指標從乙個可選引數移動到下乙個可選引數,由此,函式就可以獲取所有的可選引數。va_list 型別被定義在標頭檔案 stdarg.h 中。
這麼說可能太過官方,太抽象了。我們來舉個例子。
假設我們有乙個可變引數函式getsum(int numofpara, …),然後現在我們代入具體值,比如getsum(3, 7, 8, 9),通過上面的介紹我們知道,第乙個3是強制引數,表明後面跟了3個可變參量,而後面的7、8、9則為具體的可變參量。
根據c語言的要求,我們需要在getsum(int numofpara, …)函式中定義乙個va_list型別的指標。
然後它是怎麼實現「訪問、獲取可選引數」的呢?
c語言裡是這樣實現的:通過某種機制(等會兒會講)讓強制引數和可選引數在記憶體中以連續的方式存放(強制引數在前),同時讓va_list指標指向最後乙個強制引數,即第乙個可選引數前的強制引數。然後,通過另一種機制,每次訪問va_list所指的參量之後,指標自動向後移位。進而當下一次再訪問va_list的時候,訪問的就是下一位的值。這樣就可以訪問各個可變引數了。大概就是這樣了,講得太接地氣了。
3、具體實現函式
那麼,c語言又是怎麼實現上面提到的這些機制的呢?
由此引入兩個函式。
void va_start(va_list argptr, lastparam);
是va_list指標的初始化函式,用來初始化指標,也就是實現讓其「先指向最後乙個強制引數」的功能。
於是自然而然的,該函式的第乙個引數是乙個va_list 型別的指標,第二個引數是可變引數函式中最後乙個強制引數,即第乙個可選引數前的強制引數。
va_start函式中,va_list進行初始化,指標指向末尾的強制引數。va_start結束後,初始化完成,指標自動移位到下乙個引數,即第乙個可變引數。(雖然感覺有點奇怪)
那麼怎麼訪問va_list指標當前指向的可變引數呢?引出第二個函式:
type va_arg(va_list argptr, type);
其第乙個引數是已經初始化完成的va_list指標,第二個引數則為可變引數的型別,返回的引數就是當前va_list指標所指的可變引數,所以型別也跟傳入的可變引數型別相同。
每一次通過va_arg函式訪問完一次引數後,va_list指標會自動移位到下一位。
c語言還規定,當不再需要使用引數指標時,必須呼叫巨集 va_end來終結該指標,其實說白了就是釋放記憶體。(如果想使用巨集 va_start 或者巨集 va_copy 來重新初始化乙個之前用過的引數指標,也必須先呼叫巨集 va_end)
4、具體實現示例
好了,說了這麼多,我們通過完善之前寫了一半的getsum函式來具體了解一下,可能就會豁然開朗明明白白了:
double
getsum
(int numofpara,..
.)//同時,每次va_arg函式結束後,va_list指標指向下一位
va_end
(pointer)
;//終結指標,釋放記憶體
return sum;
有個問題要注意一下,在main函式裡呼叫的時候,應該寫成getsum(3, 7.0, 8.0, 9.0),否則得到的可能是0。
}
接下來簡單補充一下上面提到的所謂「機制」:
它的實現原理利用了記憶體的壓棧技術,將引數壓入(push)棧內,使用時,再逐個從棧裡pop出來上面說得比較粗糙,可能有一些地方說錯了。裡面具體原理我也還沒去深究。以後吧。需要注意的是,壓棧的順序是從最右邊引數開始的,再向左逐個壓入,根據棧的原理,在取引數時,就從第乙個可變引數開始了。
在程序中,堆疊位址是從高到低分配的.當執行乙個函式的時候,將引數列表入棧,壓入堆疊的高位址部分,然**棧函式的返回位址,接著入棧函式的執行**,這個入棧過程,堆疊位址不斷遞減。
(所以取的時候就是從低到高,也就是上面草圖中從左到右從3到7到8到9)
C語言函式可變引數
翻apue的時候,看到了一組可變引數的巨集就是va list 類似 void print arg int count,1 va list變數 ifdef m alpha typedef struct va list else typedef char va list 這個 endif 2 intsi...
C語言可變引數函式
今天在c和指標書中第一次接觸到這個函式,以前有碰到過這樣可變引數的問題,但無從下手。原來c語言還是有這樣的功能,感覺c真是無所不能。c函式要在程式中用到以下這些巨集 void va start va list arg ptr,prev param type va arg va list arg pt...
C語言 可變引數函式
可變引數函式,即引數個數可變的函式。返回值 函式名 固定引數m個,可變引數n個 其中,m 1,n 0,即 至少需要乙個固定引數,否則你怎麼定位到引數呢?固定引數的宣告與普通函式引數相同 可選引數由於數目不定 0個或以上 宣告時用 表示 用作引數佔位符 固定引數和可選引數共同構成可變引數函式的引數列表...