盡量不要使用可變引數

2021-08-20 19:21:49 字數 3225 閱讀 4232

在某些情況下我們希望函式引數的個數可以根據實際需要來確定,所以c語言中就提供了一種長度不確定的引數,形如:「...」,c++語言也繼承了這一語言特性。在採用ansi標準形式時,引數個數可變的函式的原型是:

typefuncname(typepara1,typepara2,...);

這種形式至少需要乙個普通的形式引數,後面的省略號(...)不能省去,它是函式原型必不可少的一部分。典型的例子有大家熟悉的printf()、scanf()函式,如下所示的就是printf()的原型:

intprintf(constchar*format,...);

除了引數format固定以外,其他引數的個數和型別是不確定的。在實際呼叫時可以有以下形式:

intyear=2011;

charstr="hello2011";

printf("thisyearis%d",year);

printf("thegreetingwordsare%s",str);

printf("thisyearis%d,andthegreetingwordsare:%s",year,str);

也許這些已經為大家所熟知,但是可變引數的實現原理卻是c語言中比較難理解的一部分。在標準c語言中定義了乙個標頭檔案,專門用來對付可變引數列表,其中,包含了乙個va_list的typedef宣告和一組巨集定義va_start、va_arg、va_end,如下所示:

//file:vc++2010中的stdarg.h

#include

#defineva_start_crt_va_start

#defineva_arg_crt_va_arg

#defineva_end_crt_va_end

//file:vc++2010中的vadefs.h

#ifndef_va_list_defined

typedefchar*va_list;

#define_va_list_defined

#endif

#ifdef__cplusplus

#define_addressof(v)(&reinterpret_cast(v))

#else

#define_addressof(v)(&(v))

#endif

#ifdefined(_m_ix86)

#define_intsizeof(n)((sizeof(n)+sizeof(int)-1)&~(sizeof(int)-1))

#define_crt_va_start(ap,v)(ap=(va_list)_addressof(v)+_intsizeof(v))

#define_crt_va_arg(ap,t)(*(t*)((ap+=_intsizeof(t))-_intsizeof(t)))

#define_crt_va_end(ap)(ap=(va_list)0)

定義_intsizeof(n)是為了使系統記憶體對齊;va_start(ap,v)使ap指向第乙個可變引數在堆疊中的位址,va_arg(ap,t)使ap指向下乙個可變引數的堆疊位址,並用*取得該位址的內容;最後變參獲取完畢,通過va_end(ap)讓ap不再指向堆疊,如圖1-3所示。

由於將va_start、va_arg、va_end定義成了巨集,可變引數的型別和個數在該函式中完全由程式**控制,並不能智慧型地進行識別,所以導致編譯器對可變引數的函式原型檢查不夠嚴格,難於查錯,不利於寫出高質量的**。

引數個數可變具有很多的優點,為程式設計師帶來了很多的方便,但是上面c風格的可變引數卻存在著如下的缺點:

(1)缺乏型別檢查,型別安全性無從談起。「省略號的本質是告訴編譯器『關閉所有檢查,從此由我接管,啟動reinterpret_cast』」,強制將某個型別物件的記憶體表示重新解釋成另

外一種物件型別,這是違反「型別安全性」的,是大忌。

例如,自定義的列印函式。

voiduserdefinedprintfun(char*format,inti,...)

elseif(*(format-1)=='%'&&*format=='d')

elseif(*(format-1)=='%'&&*format=='f')

}va_end(arg_ptr);

return;

}如果採用下面三種方法呼叫,合法合理:

userdefinedprintfun("%d",2010);//結果2010

userdefinedprintfun("%d%d",2010,2011);//結果20102011

userdefinedprintfun("%s%d","hello",2012);//結果hello2012

但是,當給定的格式字串與引數型別不對應時,強制轉型這個「怪獸」就會被喚醒,悄悄地毀壞程式的安全性,這可不是什麼高質量的程式,如下所示:

userdefinedprintfun("%d",2010.80f);

//結果2010

userdefinedprintfun("%d%d","hello",2012);

//結果150958722015(這是什麼結果???)

(2)因為禁用了語言型別檢查功能,所以在呼叫時必須通過其他方式告訴函式所傳遞引數的型別,以及引數個數,就像很多人熟知的printf()函式中的格式字串char*format。這種方式需要手動協調,既易出錯,又不安全,上面的**片段已經充分說明了這一點。

(3)不支援自定義資料型別。自定義資料型別在c++中占有較重的地位,但是長引數只能傳遞基本的內建型別。還是以printf()為例,如果要列印出乙個student型別物件的內容,對於這樣的自定義型別,該用什麼格式的字串去傳遞引數型別呢?如下所示:

classstudent

;studentxiaoli;

printf(format,xiaoli);//format應該是什麼呢

上述缺點足以讓我們有了拒絕使用c風格可變引數的念頭,何況c++的多型性已經為我們提供了實現可變引數的安全可靠的有效途徑呢!如下所示:

classprintfunction

;雖然上述設計不能像printf()函式那樣靈活地滿足各種各樣的需求,但是可以根據需求適度擴充函式定義,這樣不僅能滿足需求,其安全性也是毋庸置疑的。舍安全而求危險,這可不是明白人所為。如果還對printf()的靈活性念念不忘,我告訴大家,有些c++庫已經使用c++高階特性將型別安全、速度與使用方便很好地結合在一起了,比如boost中的format庫,大家可以嘗試使用。

請記住:

編譯器對可變引數函式的原型檢查不夠嚴格,所以容易引起問題,難於查錯,不利於寫出高質量的**。所以應當盡量避免使用c語言方式的可變引數設計,而用c++中更為安全的方式來完美代替之。

盡量不要使用FindWindow

盡量不用 findwindow 最近發現 se6和 se5程序共存時視窗名稱一樣引起的 bug。原因是我們經常使用 findwindow 來獲得視窗控制代碼,然後進行訊息通訊,這樣呼叫簡單,但增加了不同模組之間的依賴性,比如同時有兩個程序時,就可能會找錯視窗。而如果靠人去維護這個視窗名稱,在程式工程...

盡量不要使用文字模式

當我們使用c的庫函式讀取檔案時,會有文字模式和二進位制模式兩種讀取模式。一些傳輸資料的協議比如tftp ftp也有文字和二進位制模式的區分。既可以使用文字模式也可以使用二進位制模式時,盡量使用二進位制模式。文字模式和二進位制模式讀取檔案時,差別主要是在回車換行的處理上,不同系統對回車換行的處理不一致...

盡量不要使用CSS Expression的原因

在程式設計客棧csvwhlqyeiys expression中使用j ascript表示式。css屬性根據j ascript表示式的計算結果來設定。c expression在其它瀏覽器中不起作用,因此在跨瀏覽器的編碼中單獨針對ie設定時會比較有用。從ie5開始支援css expression。我們看...