(C++的對話)Solmyr 的小品文系列之二:模稜兩可的陷阱 (轉)
原作者:Solmyr Fopchome
“為什麼會這樣?!”,zero 一邊喝水一邊嘟囔著,恨恨的看著面前顯示器上的程式碼,“為什麼這麼簡單的一個也會出現編譯錯誤 …… ”
“這是因為你的設計太差!”
噗!zero 被幽靈一樣出現在背後的 Solmyr 嚇了一大跳,一口水差點全噴出來。
“咳!咳咳!S …… Solmyr ,你什麼時候站在我背後的?”,zero 很費力的平息了咳嗽,同時努力回想剛才自己有沒有把柄會被 Solmyr 抓到。
Solmyr 抓過一張椅子坐了下來:“在你一開始幹傻事的時候我就在了,正是這個糟糕的設計導致了現在困擾你的編譯錯誤。”
“哪 …… 哪裡?”
“這兒。” Solmyr 抓過鍵盤,標出了下面這段程式碼:
void SomeFunc(int i)
…………
void SomeFunc(float f)
…………
int main()
{
??…………
??SomeFunc(1.2);?????// Error! ambiguous call
??…………
}
“我也正覺得奇怪”,zero 一如既往的撓著頭,試圖壓榨不存在的智慧,“這麼簡單的一個過載,應該很清楚才對。我這裡呼叫時明明給出的是浮點數,顯然應該呼叫 float 版本的 SomeFunc 。最奇怪的是如果沒有這個呼叫,整個編譯連線完全沒有問題,可見這樣過載函式是合法的。”
“嗯,沒錯,確實是合法的,但是合法不代表正確。zero ,你念一下這一段,看看先知 Meyers 在他的《50 誡》(注:指《Effective C++ 2/e》一書)中的條款 26 中是怎樣描述 C++ 對待‘模稜兩可’的哲學的。”,Solmyr 翻開了一本書,指著其中的幾行。
“C++ ……”
“站起來,大聲念!”
zero 依言站起,中氣十足的念道:“C++ 也有一個哲學信仰:它相信潛在的模稜兩可的狀態不是一種錯誤。”
旁邊的座位來低低的竊笑聲,更遠處的人探頭張望,投來好奇的目光,zero 頓時感到自己像個傻瓜。當 zero 看到 Solmyr 嘴邊招牌式的壞笑時明白了過來:自己又一次被 Solmyr 設計了。
“嗯,明白了這一點,我們就可以展開進一步的討論了”,Solmyr 開始轉入正題,“還記得上次我說過上面的 1.2 是什麼嗎?”
zero 露出了回憶的表情:“嗯 …… 1.2 是‘寫在程式碼裡的常量’…… 應該是一個 double 型別常量。”
“這就是問題所在:看到這個呼叫函式的請求,會去尋找你的過載函式中哪個函式能夠匹配這個呼叫請求給出的引數,結果它發現沒有一個函式的引數是 double 型別的,所以必須要做型別轉換,但是 double 型別既可以轉成 int ,也可以轉成 float ,究竟轉哪個好呢?編譯器不知道,所以只好報錯了。明白了嗎?”
zero 似懂非懂的點了點頭。
“那我問你,這樣過載編譯時會不會報錯?”,Solmyr 稍稍改動了一下 zero 的程式碼:
void SomeFunc(int i)
…………
void SomeFunc(double )
…………
int main()
{
??…………
??float f = 1.2;
??SomeFunc(f);
??…………
}
zero 看了看,學著 Solmyr 的語氣說到:“編譯器發現沒有一個函式的引數是 float 型別的,所以必須要做型別轉換,但是 float 型別既可以轉成 int ,也可以轉成 double ,究竟轉哪個好呢?編譯器不知道,所以只好報錯了。”
“錯!”,Solmyr 順手按下了執行按鈕,程式執行一切正常,輸出顯示呼叫的是 double 版本的 SomeFunc 函式。
zero 再度感到了困惑:“為什麼同樣是要選擇型別轉換,這個就沒錯,前一個就有錯呢?這中間的邏輯何在?”
“重要的是這一句:‘究竟轉哪個好呢?編譯器不知道’。你沒有注意到我說這句話的時候‘好’字上用了一個重音嗎?”
“你用過重音嗎?”
“ …… 這個不是重點。重點在於,float 到 int 和 float 到 double 這兩個轉換,編譯器是能夠選擇的,因為 float 到 int 會損失資料 —— 象樣的編譯器會在做這種型別轉換的時候給出一個 warning —— 而 float 到 double 則不損失資料,所以編譯器知道‘轉哪個好’。而之前的情況,double 到 int 到 float 的轉換都要損失資料,所以編譯器不知道‘轉哪個好’,它沒辦法做一個決定 —— ”,Solmyr 看了看 zero ,再度問道,“明白了嗎?”
zero 皺著眉頭,撓頭撓的更起勁了,顯然對於消化一下子出現的這麼多資訊感到少許困難:“我想我明白了,關鍵是編譯器能否區分兩個型別轉換。在這裡區分的關鍵是型別轉換是否損失資料,嗯 …… 所以我只要在所有用到浮點數的場合都使用 double 型別,就不會有問題,即使別人用 float 來呼叫也一樣。”
“正確。不過‘模稜兩可’的問題可不僅僅出現浮點數身上,例如,這樣兩個過載函式 …… ”,Solmyr 接著鍵入:
void SomeFunc(double db)
void SomeFunc(char ch)
“如果我用一個整形變數來呼叫,會出現什麼事情?”,Solmyr 扭頭盯著 zero。
“呃 …… 編譯器同樣無法區分 int 到 double 和 int 到 char 這兩個型別轉換,所以同樣會報錯。”
“正確。你能夠自己舉出幾個例子嗎?”,Solmyr 把鍵盤遞了回去。
很明顯的,zero 陷入了沉思,過了一會兒,螢幕上出現了這樣幾行程式碼:
// 用 int 呼叫的話會出錯
void fun(char ch)
void fun(int* pi)???// 或者其他指標
// 用 int 呼叫同樣會出錯
void fun(double db)
void fun(int* pi)???// 或者其他指標
“嗯,很好。不過你還是漏了一種重要情況,”,Solmyr 補充道,“就是引數有預設值的時候:”
// 呼叫時如果不給引數會出錯
void fun(int i=10)
void fun()
“天哪!”,zero 看起來快要崩潰了,“居然有這麼多模稜兩可的陷阱,這叫我怎樣釋出我的函式?在文件裡寫:以下 153 種呼叫方式將導致編譯錯誤嗎?”
“不要這麼緊張,”,Solmyr 好整以暇的說到,“過載函式的模稜兩可現象不是不能避免的,辦法有兩個:一是用模板來代替過載,尤其是象你的 SomeFunc 這樣 int 型和 double 型處理演算法相同的情況;二是如果要用過載的話,儘可能保證函式的引數個數不同。”
“可是如果處理演算法不一樣,函式需要的引數個數又相同,那該怎麼辦?”
“很簡單,加入‘無用的引數’,象這樣:”
void SomeFunc(float db, int)
void SomeFunc(int i)
“第一個函式的第二個引數沒有任何作用,所以你可以乾脆不給它命名,只要宣告一下有這個 int 型引數就可以了。文件裡可以這樣寫:該引數是為今後升級預留的餘地,呼叫時請傳入 0 值。”
“ …… 你的文件裡大概都是這一類的話吧 …… 啊!好痛!這回又是一本書!”,zero 被 Solmyr 突如其來的襲擊擊中,發出了悲慘的哀鳴。
“你得感謝先知 tt Meyers,他的《 50 誡》輕而薄,我手上拿的若是一本教主 Bjarne Stroustrup 的《聖經》(注:指《The C++ Programing Language 3/e》一書,Bjarne Stroustrup 是 C++ 語言的設計者),你現在已經爬不起來了。”,Solmyr 再度披上了修養的偽裝,不過言辭中仍然留著一點點殺氣的痕跡 ……
“真是殘暴的傢伙 ……”,zero 小聲嘟囔著。
“你說什麼?”,殺氣再度升高。
“不,不!我什麼也沒說!”,zero 連忙否認,試著轉移話題,“啊!我懂了,要避免模稜兩可的陷阱,一是用模板來替代過載,二是利用加入‘無用的引數’這一手段保證過載函式引數個數不同。這樣就可以避開模稜兩可的問題,是不是,Solmyr 老師?”。zero 很努力的裝出天真無邪的樣子。
“ …… 真是拙劣的演技 …… ”,Solmyr 心中暗想。“不完全,上述手段只能解決函式過載這一塊而已,模稜兩可問題涉及的情況要廣泛的多,比如《 50 誡》中的例子:”
class B;??????// 前置宣告
class A
{
public:
??A(const B&);??// A 可以根據 B 構造出來
};
class B
{
public:
??operator A() const;??// B 可以被轉換為 A
};
“這兩個類本身沒有什麼問題,但若是有個函式需要 A 的作為引數,傳過去的卻是個 B 的物件時:”
void f(const A&)
B b;
f(b);??// Error! ambiguous call
“注意到這裡面的問題了嗎?有兩種一樣好方法可以完成轉換,一是用 A 的建構函式以 b 為引數構造一個新的 A 類物件,而是呼叫 B 的轉換函式將 b 轉換為一個 A 類物件。編譯器再度無法區分哪個轉換更好,只能報錯了。後面還有一個多重繼承的例子,你自己看吧”
“這 …… 這 ……”,zero 剛剛建立起來的對迴避陷阱的自信再度崩塌。
“要回避一切模稜兩可的問題是不可能的,”,Solmyr 站起身來,“關鍵是瞭解它為什麼會發生,怎樣的情況容易誘發它,然後小心的加以處理,C++ 中很多問題都是如此。這塊《 50 誡》的石板就留給你了,好好研讀吧。哈哈哈哈!”,Solmyr 一邊笑著一邊離開了 zero ,背影看起來像是一位飄然遠去的高人 ……
“什麼呀!根本就只是一個性格殘暴的傢伙而已,裝模做樣 …… 啪!”,zero 話音未落,一個夾劃破空氣飛來,正中 zero 的面門。
“嗚 ~ 我什麼也沒說 ~”,zero 無力的辨白,然而換來的只是旁邊的座位上再度傳來低低的竊笑聲而已。zero 明白,今天他的形象算是徹底的毀了 ……
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-1004733/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Solmyr 的小品文系列之三:物件計數(上) (轉)物件
- Solmyr 的小品文系列之四:物件計數(下) (轉)物件
- ZWeily的小品文(二)C++入門教程(1) (轉)C++
- ZWeily的小品文(三)C++入門教程(2) (轉)C++
- ZWeily的小品文(四)C++入門教程(3) (轉)C++
- ZWeily的小品文(五)C++入門教程(4) (轉)C++
- 一個C++專案的Makefile編寫-Tony與Alex的對話系列C++
- 虛擬空間的陷阱--對微軟的檄文 (轉)微軟
- 【C++】【MFC】模態和非模態對話方塊C++
- c++隱式型別轉換存在的陷阱C++型別
- c#中的模態對話方塊和非模態對話方塊C#
- Qt下的模態和非模態對話方塊QT
- 模態對話方塊與非模態對話的幾種銷燬方法與區別薦
- 網友對sars病毒事件的對話(轉)事件
- C/C++ 中的算術及其陷阱C++
- Solmyr和Zero的故事 —— 記憶體,最後一塊 (轉)記憶體
- Linux C++ 自學筆記之二<菜鳥初學系列> (轉)LinuxC++筆記
- C++的救贖 C++開源程式庫評話(轉)C++
- Haier數字可視對講系列(轉)AI
- 關於openssl應用的對話 (轉)
- C++箴言:理解typename的兩個含義(轉)C++箴言
- ZWeily的小品文(一)MFC中的檔案讀寫問題 (轉)
- C#模擬C++模板特化對型別的值的支援C#C++型別
- 什麼是AOP系列之二:AOP與許可權控制實現(轉)
- Borland與Microsoft關於Delphi的對話 (轉)ROS
- 對話方塊中對成批控制元件的操作 (轉)控制元件
- 績效稜柱模型(轉載)模型
- 從“稜鏡門”可以透視出資料安全的應對策略
- 兩句話理解js中的thisJS
- 對話方塊背景色的設定 (轉)
- 火眼金睛選培訓系列之二:教學的謊言 (轉)
- DirectShow系列講座之二——Filter原理 (轉)Filter
- 優秀技術人的管理陷阱(轉)
- C++批判系列5--繼承的本質 (轉)C++繼承
- CTI的典型應用之二 (轉)
- C/C++返回內部靜態成員的陷阱薦C++
- 關於專案經理的職業發展的對話(轉)
- C++程式設計思想筆記之二 (轉)C++程式設計筆記