對比於C語言的函數,C++增加了重載(overloaded)、內聯(inline)、const和virtual四種新機制。其中重載和內聯機制既可用於全域函數也可用於類的成員函數,const與virtual機制僅用於類的成員函數。 重載和內聯肯定有其好處才會被C++語言採納,但是不可以當成免費的午餐而濫用。本章將探究重載和內聯的優點與局限性,說明什麼情況下應該採用、不該採用以及要警惕錯用。
8.1 函數重載的概念
8.1.1 重載的起源
自然語言中,一個詞可以有許多不同的含義,即該詞被重載了。人們可以通過上下文來判斷該詞到底是哪種含義。“詞的重載”可以使語言更加簡練。例如“吃飯”的含義十分廣泛,人們沒有必要每次非得說清楚具體吃什麼不可。別迂腐得象孔已己,說茴香豆的茴字有四種寫法。
在C++程式中,可以將語義、功能相似的幾個函數用同一個名字表示,即函數重載。這樣便於記憶,提高了函數的易用性,這是C++語言採用重載機制的一個理由。例如樣本8-1-1中的函數EatBeef,EatFish,EatChicken可以用同一個函數名Eat表示,用不同類型的參數加以區別。
void EatBeef(…); // 可以改為 void Eat(Beef …);
void EatFish(…); // 可以改為 void Eat(Fish …);
void EatChicken(…); // 可以改為 void Eat(Chicken …);
樣本8-1-1 重載函數Eat
C++語言採用重載機制的另一個理由是:類的建構函式需要重載機制。因為C++規定建構函式與類同名(請參見第9章),建構函式只能有一個名字。如果想用幾種不同的方法建立對象該怎麼辦?別無選擇,只能用重載機制來實現。所以類可以有多個同名的建構函式。
8.1.2 重載是如何?的?
幾個同名的重載函數仍然是不同的函數,它們是如何區分的呢?我們自然想到函數介面的兩個要素:參數與傳回值。
如果同名函數的參數不同(包括類型、順序不同),那麼容易區別出它們是不同的函數。
如果同名函數僅僅是傳回值類型不同,有時可以區分,有時卻不能。例如:
void Function(void);
int Function (void);
上述兩個函數,第一個沒有傳回值,第二個的傳回值是int類型。如果這樣調用函數:
int x = Function ();
則可以判斷出Function是第二個函數。問題是在C++/C程式中,我們可以忽略函數的傳回值。在這種情況下,編譯器和程式員都不知道哪個Function函數被調用。
所以只能靠參數而不能靠傳回值類型的不同來區分重載函數。編譯器根據參數為每個重載函數產生不同的內部標識符。例如編譯器為樣本8-1-1中的三個Eat函數產生象_eat_beef、_eat_fish、_eat_chicken之類的內部標識符(不同的編譯器可能產生不同風格的內部標識符)。
如果C++程式要調用已經被編譯後的C函數,該怎麼辦?
假設某個C函數的聲明如下:
void foo(int x, int y);
該函數被C編譯器編譯後在庫中的名字為_foo,而C++編譯器則會產生像_foo_int_int之類的名字用來支援函數重載和型別安全串連。由於編譯後的名字不同,C++程式不能直接調用C函數。C++提供了一個C串連交換指定符號extern“C”來解決這個問題。例如:
extern “C”
{
void foo(int x, int y);
… // 其它函數
}
或者寫成
extern “C”
{
#include “myheader.h”
… // 其它C標頭檔
}
這就告訴C++編譯譯器,函數foo是個C串連,應該到庫中找名字_foo而不是找_foo_int_int。C++編譯器開發商已經對C標準庫的標頭檔作了extern“C”處理,所以我們可以用#include 直接引用這些標頭檔。
注意並不是兩個函數的名字相同就能構成重載。全域函數和類的成員函數同名不算重載,因為函數的範圍不同。例如:
void Print(…); // 全域函數
class A
{…
void Print(…); // 成員函數
}
不論兩個Print函數的參數是否不同,如果類的某個成員函數要調用全域函數Print,為了與成員函數Print區別,全域函數被調用時應加‘::’標誌。如
::Print(…); // 表示Print是全域函數而非成員函數