“為什么會這樣?!”,zero
一邊喝水一邊嘟囔著,恨恨的看著面前顯示器上的代碼,“為什么這么簡單的一個調用也會出現編譯錯誤
…… ”
“這是因為你的設計太差!”
噗!zero
被幽靈一樣出現在背后的 Solmyr
嚇了一大跳,一口水差點全噴出來。
“咳!咳咳!S
…… Solmyr ,你什么時候站在我背后的?”,zero
很費力的平息了咳嗽,同時努力回想剛才自己有沒有把柄會被
Solmyr 抓到。
Solmyr
抓過一張椅子坐了下來:“在你一開始干傻事的時候我就在了,正是這個糟糕的設計導致了現在困擾你的編譯錯誤。”
“哪
…… 哪里?”
“這兒。”
Solmyr 抓過鍵盤,標出了下面這段代碼:
void SomeFunc(int
i)
…………
void SomeFunc(float f)
…………
int main(void)
{
…………
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 db)
…………
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
突如其來的襲擊擊中,發出了悲慘的哀鳴。
“你得感謝先知
Scott 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
明白,今天他的形象算是徹底的毀了
……