幾年前,我已經介紹過如何使用const和volatile限定詞聲明數據。使用這些限定詞來聲明數據,產生的影響會波及到函數的聲明。在C和C++中,影響是不一樣的,很大程度上是因為C++中的函數聲明比C有更多的功能。為了更清楚描述清楚影響的不同,我們先來看看C和C++中函數最主要的差異是什么。
很多C庫包含至少一組的命名非常接近的函數。每個函數除了參數或返回值不同,功能在本質上是相同的。
標准C庫也包含若干這樣的組。例如,有一組“abs”函數專門用來計算一個數值的絕對值。組里包含:
int abs(int i); long int labs(long int li); float fabsf(float f); double fabs(double d); long double fabsl(long double ld);
函數fabsf和fabsl不屬於早期C標准,卻存在於修訂后的C9X標准中。
標准C庫還有一個“put”函數組:
int fputc(int c, FILE *f); int fputs(char const *s, FILE *f); int putchar(int c); int puts(char const *s);
這些函數都用來往文件里寫入,盡管,寫的內容的類型不同,文件的指定方式也有差異。 函數fputc和putchar每次寫一個字節(傳入的其實是int),而puts和fputs每次寫從null結尾字符串中得到的所有字節。函數fputc和fputs寫入的文件是由參數傳入的,而putchar和puts總是往標准輸出中寫。
盡管組中的函數名字不同,但程序員還是把它們當做只有一個名字的函數。例如,從來沒聽過程序員說fputd一個字符或fputs一個字符串,而是說put一個字符或put一個字符串。" 我們通常把函數fputc和fputs當做一個put函數。我們也通常把函數abs。當我們已經明了組里的每一個函數實際上都是不同的函數,那么沒有理由再啰嗦下去了。
重載聲明
讓函數名字跟描述程序行為的名字保持一致是一個良好的編程習慣。每種負責輸出的函數最好是都叫做put。不幸的是,C不允許程序中有同名的函數。
C中獨一無二函數名的限定對函數庫的使用者和作者都是一種負擔。作者需要想象出相近但差異又不能太大的函數名,而使用者需要學會這些不同。一個認真的作者會浪費數小時來設計一組函數名前綴或后綴,以便減少使用者的負擔。
C++通過重載函數名來避免這類麻煩。你可以在同一個程序里使用同名的兩個或多個函數。函數名重載可以讓函數使用起來更“自然”。使用了重載的程序也更容易讀和寫。(當然,過猶不及,過多的重載也並非好事)
C++中聲明重載的函數跟聲明其它函數沒什么不同。只不過它跟其它的某個函數重名。重載函數必須使用不同的變量,否則編譯器沒法區分它們。
例如,可以定義如下的重載函數put:
int put(int c); // putchar int put(int c, FILE *f); // fputc int put(char const *s); // puts int put(char const *s, FILE *f); // fputs
重載決策
當編譯器遇到對函數put的調用,它會選擇函數參數完全匹配的函數聲明進行調用。這個過程叫做重載決策。例如:
put('a', stdout);
調用如下的函數聲明:
int put(int c, FILE *f);
而:
put("Hello\n");
調用:
int put(char const *s);
如果編譯器沒法找到參數匹配的函數,會報錯。例如,調用:
put("Error #", n, stderr);
會報錯,因為沒有函數聲明了三個變量。
盡管調用的函數的參數個數必須跟匹配的聲明函數一致,但參數類型卻不必完全一致。C++允許進行一些參數的隱式類型轉換。
例如,函數聲明如下:
int put(int c); int put(int c, FILE *f);
往一個文件中寫入單個字符。在兩個函數中,參數c的類型是int而不是char,標准C庫使用整型的EOF來表示文件結尾和I/O錯誤。一個char沒辦法表示EOF。一個int可以表示所有的char值和EOF。
盡管函數聲明了int型參數,但你可以使用char參數來調用。例如:
char c; ... put(c);
重載決策選擇了:
int put(int c);
作為匹配函數。這里,編譯器把char隱式轉換為int。
最佳匹配和歧義Best matches and ambiguities
有可能會有多個重載函數能夠匹配一個函數調用。例如,下面的四個函數:
void f(int i); void f(long int li); void f(char *p); void f(double d, int i);
開頭的三個函數可以匹配函數調用f(0)。0可以被看做int,long int或char*類的空指針。第四個函數沒法匹配,因為它需要兩個參數。
當面對多個可選函數時,重載決策根據函數參數類型的隱式轉換來排序,找出最匹配的那個。例如,在調用f(0)時,參數0位int類型,當調用:
void f(char *p);
需要把int轉換為char*。當調用:
void f(long int li);
需要把int轉換為long int。而:
void f(int i);
是非常准確的匹配。准確的匹配永遠是最佳選擇。
那么,假設:
void f(int i);
沒有被聲明。這種情況下,重載決策必須在下面二者中選一個:
void f(long int li); void f(char *p);
但它們兩個不分仲伯,都需要進行類型轉換。當重載決策沒有辦法選出一個最優匹配,那么調用就是不明確的,這會產生一個編譯錯誤。
這就是函數名重載的基本知識,更多細節請見下次的文章。