下面有關派生類與基類中存在同名函數 fn:
1 class A 2 { 3 public: 4 void fn() 5 {} 6 7 void fn(int a) 8 {} 9 }; 10 11 class B : public A 12 { 13 public: 14 void fn() 15 {} 16 }; 17 18 int main() 19 { 20 B b; 21 b.fn(3); 22 return 0; 23 }
1、以上代碼編譯為什么不能通過? (問題在第21行,編譯器會報怨說,B中,並不存在fn(int)的函數)。
2、編譯器這樣做(即不允許通過這樣的代碼)的好處是什么?
相信這是一個非常之普遍的問題了,在眾多經典的C++書籍中,都會將之列為一個重要C++問題,詳細地深入地講解。我這里僅能簡單回答,可能對有同樣疑問的同學,有個快速了解的作用。由於出差在外,非常不方便,回答問題時既不能詳細調試,也不能做必要的查經求典的動作(手頭沒書),犯錯的地方,請大家指正,我會及時修訂。
回答如下:
你涉及到一個C++中的重要的知識點。即:同名函數的重載動作,只發生在自由函數(即非成員),及同一個class/struct內部的函數之間。而不能跨越基類和派生類。當派生類寫一個和基類同名(無論參數列表相同或不相同)的函數時,此時發生的動作叫“覆蓋”。覆蓋的意思,就是基類的同名函數,在派生類內,將變得無法直接調用(但可以間接調用)。
首先,我們還是針對問題的本質,簡化一下代碼,拋棄無直接相關的枝節。
1 struct A 2 { 3 void foo(int d) 4 { 5 cout << "A::foo - int" << endl; 6 cout << d << endl; 7 } 8 }; 9 10 struct B : public A 11 { 12 void foo(double d) //覆蓋了A::foo(int d); 13 { 14 cout << "B::foo - double" << endl; 15 cout << d << endl; 16 } 17 }; 18 19 int main() 20 { 21 A a; 22 a.foo(10); 23 24 B b; 25 b.foo(10.2); 26 b.foo(2); //調用的仍然是B::foo,雖然2明顯是個整數 27 28 return 0; 29 }
以上代碼,運行之后輸出結果大致如下:(注釋為后加內容)
A::foo - int
10
B::foo - double
10.2
B::foo - double //調用的仍然是B::foo,雖然2明顯是個整數
2
那么,要如何才能調用基類的foo(int )呢?
方法有兩種,其一為“臨時法”:
B b; b.A::foo(2); //顯式調用A范圍內的foo
其二就該叫“終身法”,哈哈這名字又是我瞎起的,更好的叫法,應叫“引狼入室法”,別掰了。回憶一下 namespace 的三種用法,其中一種稱為“using declaration/使用聲明”, 這里可以用上類似的代碼(很多情況下,class/struct域,和一個namespace有相同的功能)。請看代碼:
1 struct B : public A 2 { 3 using A::foo; //通過“使用聲明”,引入了A::foo…… 4 5 void foo(double d) 6 { 7 cout << "B::foo - double" << endl; 8 cout << d << endl; 9 } 10 };
現在要調用時:
1 int main(void) 2 { 3 B b; 4 5 b.A::foo(3); //調用的……當然是A::foo(int) 6 b.foo(2); //調用的……也是A::foo(int) 7 8 b.foo(10.234); //調用的……B::foo(double) 9 10 return 0; 11 }
接下回答“編譯器這樣做的好處是什么?”
這是為了避免“非惡意性的錯誤”。這也是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~~~
原文地址:基類和派生類之間的同名函數,存在重載嗎?
and more:c++(重載、覆蓋、隱藏)