基類和派生類之間的同名函式,存在過載嗎?
下面有關派生類與基類中存在同名函式fn:
Code:- class A
- {
- public:
- void fn()
- {}
- void fn(int a)
- {}
- };
- class B : public A
- {
- public:
- void fn()
- {}
- };
- int main()
- {
- B b;
- b.fn(3);
- return 0;
- }
他的疑問是:
1、以上程式碼編譯為什麼不能通過? (問題在第21行,編譯器會報怨說,B中,並不存在fn(int)的函式)。
2、編譯器這樣做(即不允許通過這樣的程式碼)的好處是什麼?
相信這是一個非常之普遍的問題了,在眾多經典的C++書籍中,都會將之列為一個重要C++問題,詳細地深入地講解。我這裡僅能簡單回答,可能對有同樣疑問的同學,有個快速瞭解的作用。由於出差在外,非常不方便,回答問題時既不能詳細除錯,也不能做必要的查經求典的動作(手頭沒書),犯錯的地方,請大家指正,我會及時修訂。
回答如下:
你涉及到一個C++中的重要的知識點。即:同名函式的過載動作,只發生在自由函式(即非成員),及同一個class/struct內部的函式之間。而不能跨越基類和派生類。當派生類寫一個和基類同名(無論引數列表相同或不相同)的函式時,此時發生的動作叫“覆蓋”。覆蓋的意思,就是基類的同名函式,在派生類內,將變得無法直接呼叫(但可以間接呼叫)。
首先,我們還是針對問題的本質,簡化一下程式碼,拋棄無直接相關的枝節。
Code:- struct A
- {
- void foo(int d)
- {
- cout << "A::foo - int"
- cout << d << endl;
- }
- };
- struct B : public A
- {
- void foo(double d) //覆蓋了A::foo(int d);
- {
- cout << "B::foo - double" << endl;
- cout << d << endl;
- }
- };
- int main()
- {
- A a;
- a.foo(10);
- B b;
- b.foo(10.2);
- b.foo(2); //呼叫的仍然是B::foo,雖然2明顯是個整數
- return 0;
- }
以上程式碼,執行之後輸出結果大致如下:(註釋為後加內容)
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有相同的功能)。請看程式碼:
- struct B : public A
- {
- using A::foo; //通過“使用宣告”,引入了A::foo……
- void foo(double d)
- {
- cout << "B::foo - double" << endl;
- cout << d << endl;
- }
- };
現在要呼叫時:
- int main(void)
- {
- B b;
- b.A::foo(3); //呼叫的……當然是A::foo(int)
- b.foo(2); //呼叫的……也是A::foo(int)
- b.foo(10.234); //呼叫的……B::foo(double)
- return 0;
- }
接下回答“編譯器這樣做的好處是什麼?”
這是為了避免“非惡意性的錯誤”。這也是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~~~
-------------------------------------