天天看點

《企業級ios應用開發實戰》一3.6 可變參數

我們知道,c和c++語言支援可變參數的函數,例如我們常用的nslog和printf函數。objective-c作為c語言的超集,當然毫無例外地也支援可變參數。迄今為止,我們至少用過了一種使用可變參數的方法,即nsstring的stringwithformat:方法。

c語言通過stdarg.h庫支援可變參數,objective-c 也不例外。在c語言中,如果你要使用可變參數,必須包含頭檔案stdarg.h,但在cocoa中卻不必,因為蘋果已經在 nsobjc runtime.h中包含了stdarg.h。

stdarg.h的定義如下:

首先定義了一個va_list類型,其實就是一個void,即可以指向任何類型的指針。你可以把它看成是char,因為char*實際上也可以指向任何記憶體單元的位址。

然後是3個預定義宏,va_start、va_end和va_arg。可以看出stdarg.h完全是以“預定義宏”這種“古老”的方式來支援可變參數的。

接下來我們看一個例子,該方法使用了一個可變參數,并将這些可變參數進行了累加,然後傳回一個nsnumber:

代碼說明:

第1行是方法定義,該定義應當加到頭檔案中。省略号...表明方法接收一系列數目不定的參數,在...前面至少需要指定一個任意類型的參數。有時我們必須知道參數的個數以防止出現無效的引用,但在某種情況下,參數個數是可以通過其他參數推斷出來的(例如nslog或printf函數可以通過計算%号的個數推斷可變參數的個數),或者對于nsmutablearray來說,它總是以nil終止。

如果是最後一種方法,我們可以把方法重新定義為:

這樣,我們就可以用以下調用方式代替“addvalues:3,num1,num2,num3”:

這樣,我們就可以省略第1個表示可變參數個數的int參數。

第2行中的va_list是void *類型,是以它實際上是一個可變的對象數組。

第3行用args來存放可變參數清單,而count則表示函數最後一個參數(即第一個“固定參數”)。這将使編譯器把args指向第1個參數後的位置(通過count位址加上count變量的長度)。

很奇怪嗎?可變參數中第1個參數的位址為什麼是“count位址+count的長度”?因為對于大多數c編譯器,函數棧中參數的存放順序是從右到左的,也就是說先放入可變參數的最後一個參數,再放可變參數的倒數第2個參數……,然後放可變參數的第1個參數,最後是固定參數count。而與此同時,棧的方向是向下的,即先入棧的資料位于高位址,後入棧的資料則位于棧的起始位址。這樣,實際上最後放入的固定參數count的位址變成了棧的起始位址。而緊随count之後的位址則是可變參數的第1個參數位址,即“count位址+count的長度”,是以編譯器要能找到第1個可變參數的位址,隻要知道1個參數:count就夠了,由count取得函數棧的起始位址,加上sizeof(count),得到第1個可變參數的位址。va_start的第1個參數args是一個輸出參數,經過va_start調用之後,args将等于arg_start計算出來的第1個可變參數的位址。

第6行是一個for循環,因為我們無法通過 args 自身推斷 args 的大小,是以必須顯式地用count來指定args的大小。或者可以使用nil終止的清單來檢索可變參數。

如果你使用nil終止的數組作為可變參數,則應該用下面一行來代替第6~7行:

第 7 行将 args 中的下一個參數放入 value,并顯式地轉為 nsnumber*(如果不知道類型,可以用id)。

第10行表明,一旦使用完args清單,就關閉它 。

提示:如果你使用va_arg(args,double)(或者float等其他原始類型),那麼當你試圖傳遞一系列整數作為參數時(例如:addvalues:4,4,3,2,1),可能會出現一些古怪的結果。而如果你顯式地将這些參數說明為double(例如,double num1,double num2,double num3,double num4)則不會有什麼問題。

這是因為,如果編譯器看到一個方法有一個double參數但你卻傳遞了一個整數給這個方法時,它會進行類型轉換。但如果方法使用了可變參數,編譯器無法知道參數所使用的類型,是以編譯器隻會簡單地把參數作為整型處理。

繼續閱讀