1. 程式人生 > >基類和派生類之間的同名函式,存在過載嗎?

基類和派生類之間的同名函式,存在過載嗎?

第2學堂的一位朋友,在BBS上發貼

下面有關派生類與基類中存在同名函式fn:

Code:
  1. class A  
  2. {  
  3. public:  
  4.     void fn()  
  5.     {}  
  6.     void fn(int a)  
  7.     {}  
  8. };  
  9. class B : public A  
  10. {  
  11. public:  
  12.     void fn()   
  13.     {}  
  14. };  
  15. int main()  
  16. {  
  17.     B b;  
  18.     b.fn(3);  
  19.     return 0;  
  20. }  

他的疑問是:

 1、以上程式碼編譯為什麼不能通過? (問題在第21行,編譯器會報怨說,B中,並不存在fn(int)的函式)。

 2、編譯器這樣做(即不允許通過這樣的程式碼)的好處是什麼?

相信這是一個非常之普遍的問題了,在眾多經典的C++書籍中,都會將之列為一個重要C++問題,詳細地深入地講解。我這裡僅能簡單回答,可能對有同樣疑問的同學,有個快速瞭解的作用。由於出差在外,非常不方便,回答問題時既不能詳細除錯,也不能做必要的查經求典的動作(手頭沒書),犯錯的地方,請大家指正,我會及時修訂。

回答如下:


你涉及到一個C++中的重要的知識點。即:同名函式的過載動作,只發生在自由函式(即非成員),及同一個class/struct內部的函式之間。而不能跨越基類和派生類。當派生類寫一個和基類同名(無論引數列表相同或不相同)的函式時,此時發生的動作叫“覆蓋”。覆蓋的意思,就是基類的同名函式,在派生類內,將變得無法直接呼叫(但可以間接呼叫)。

首先,我們還是針對問題的本質,簡化一下程式碼,拋棄無直接相關的枝節。

Code:
  1. struct A  
  2. {  
  3.     void foo(int d)  
  4.     {  
  5.         cout << "A::foo - int"
     << endl;  
  6.         cout << d << endl;  
  7.     }  
  8. };  
  9. struct B : public A  
  10. {  
  11.     void foo(double d) //覆蓋了A::foo(int d);
  12.     {  
  13.         cout << "B::foo - double" << endl;  
  14.         cout << d << endl;  
  15.     }  
  16. };  
  17. int main()  
  18. {     
  19.     A a;  
  20.     a.foo(10);  
  21.     B b;  
  22.     b.foo(10.2);  
  23.     b.foo(2); //呼叫的仍然是B::foo,雖然2明顯是個整數
  24.     return 0;  
  25. }  

以上程式碼,執行之後輸出結果大致如下:(註釋為後加內容)
A::foo - int
10
B::foo - double
10.2
B::foo - double //呼叫的仍然是B::foo,雖然2明顯是個整數
2

那麼,要如何才能呼叫基類的foo(int )呢?

方法有兩種,其一為“臨時法”:

Code:
  1. B b;  
  2. b.A::foo(2); //顯式呼叫A範圍內的foo


其二就該叫“終身法”,哈哈這名字又是我瞎起的,更好的叫法,應叫“引狼入室法”,別掰了。回憶一下 namespace 的三種用法,其中一種稱為“using declaration/使用宣告”, 這裡可以用上類似的程式碼(很多情況下,class/struct域,和一個namespace有相同的功能)。請看程式碼:
 

Code:
  1. struct B : public A  
  2. {  
  3.     using A::foo; //通過“使用宣告”,引入了A::foo……
  4.     void foo(double d)  
  5.     {  
  6.         cout << "B::foo - double" << endl;  
  7.         cout << d << endl;  
  8.     }  
  9. };  


現在要呼叫時:

Code:
  1. int main(void)  
  2. {  
  3.    B b;  
  4.    b.A::foo(3); //呼叫的……當然是A::foo(int)
  5.    b.foo(2);    //呼叫的……也是A::foo(int)
  6.    b.foo(10.234); //呼叫的……B::foo(double)
  7.    return 0;  
  8. }  

接下回答“編譯器這樣做的好處是什麼?”


這是為了避免“非惡意性的錯誤”。這也是C++語言設計中的一個重要原則:語法規則,會盡量讓程式設計師避免“無意的錯誤”,但並不去管“有意,惡意,不懷好意的錯誤”(點選看本站一此類例子)

以A,B程式碼為例,想像一下,如果我是A的作者,你是B的作者。

由於foo並不是“virtual/虛”函式,所以二者之間可能是各寫各的。如果一開始我沒有在A內寫那個foo(int)函式,而你因為需要,在B中寫了一個foo(double)函式,並且用起來很爽----因為此時基類中沒有同名函式,所以你無論寫 foo(2)也好,還是寫foo(2.0)也好,由於int 到 double的轉換是安全的,所以,兩次都非常爽地呼叫了B自己的foo(double) 。通常這也是我們所要的。

接著有一天,作為開發小組成員,我在修改A時,我覺得需要一個 void foo(int);於是我在A中加了這個函式。並且由於它不是virtual,最主要是: 由於我是基類的作者,我哪管得了天下到底有幾個人派生了我的A類呢? 所以我才懶得告訴你A類中多了一個很普通的函式。但現在情況如何呢?如果按派生類和基類的普通同名函式也可以構成過載關係,完了完了,當你拿到A的新版,重新編譯專案,一切正常,編譯器不報任何錯,可是你前面所說的那段程式碼卻突然改為呼叫基類的那個,我剛剛寫的同名函式,這還了得!

通常,基類的作者,都是比較牛逼的人,為什麼?因為他肩負著更多的責任。當一個類,以“基類”的形式提供出去以後,通常,它就不應該——有同學搶話說:“它就不應該再修改”——那倒不是(我們又不是在寫COM元件),基類也要發展,也有版本升級,否則類庫如何取得進步? 正確要求是:通常,它所做的改動,都應該是向前相容的。即基類的修改,可以為派生類提供更多的新功能,但不應該影響了派生類原來已有的功能。在此要求下,如果C++的在本例中的規則是:寫基類的人只是根據自己需要,寫了一個普普通通的成員函式,結果就造成了派生類的原有行為被偷偷地修改,那這也太為難基類作者,他最終會每寫一個函式,都使勁猜測會不會存在一個亞洲的,美洲的,南極大陸上的某個派生類的作者,在過去,或現在,或將來的時間寫的某個函式正好同名,這太累了!基類的作者再牛,但你也不該把他逼到這份上啊!

C++之父是英明的!OH Yeah~~~

-------------------------------------