類成員函式的過載、覆蓋與隱藏
阿新 • • 發佈:2019-02-10
基類和派生類類成員函式的過載、覆蓋(override)與隱藏很容易混淆,C++程式設計師必須要搞清楚概念,否則錯誤將防不勝防。
1.過載與覆蓋 成員函式被過載的特徵: (1)相同的範圍(在同一個類中); (2)函式名字相同; (3)引數不同; (4)virtual 關鍵字可有可無。 覆蓋是指派生類函式覆蓋基類函式,特徵是: (1)不同的範圍(分別位於派生類與基類); (2)函式名字相同; (3)引數相同; (4)基類函式必須有virtual 關鍵字。舉例說明:
函式Base::f(int)與Base::f(float)相互過載,而Base::g(void) 被Derived::g(void)覆蓋。 #include <iostream.h> class Base { public: void f(int x){ cout << "Base::f(int) " << x << endl; } void f(float x){ cout << "Base::f(float) " << x << endl; } virtual void g(void){ cout << "Base::g(void)" << endl;} }; class Derived : public Base { public: virtual void g(void){ cout << "Derived::g(void)" << endl;} }; void main(void) { Derived d; Base *pb = &d; pb->f(42); // Base::f(int) 42 pb->f(3.14f); // Base::f(float) 3.14 pb->g(); // Derived::g(void) }
總而言之,普通函式通過物件或指標的型別來找所呼叫的函式,而虛擬函式是通過一個vptr指標來找到所要呼叫的函式的。
2.令人迷惑的隱藏規則
本來僅僅區別過載與覆蓋並不算困難,但是C++的隱藏規則使問題複雜性陡然增加。 這裡“隱藏”是指派生類的函式遮蔽了與其同名的基類函式,規則如下: (1)如果派生類的函式與基類的函式同名,但是引數不同。此時,不論有無virtual 關鍵字,基類的函式將被隱藏(注意別與過載混淆)。 (2)如果派生類的函式與基類的函式同名,並且引數也相同,但是基類函式沒有virtual 關鍵字。此時,基類的函式被隱藏(注意別與覆蓋混淆)。
例子:
#include <iostream.h> class Base { public: virtual void f(float x){ cout << "Base::f(float) " << x << endl; } void g(float x){ cout << "Base::g(float) " << x << endl; } void h(float x){ cout << "Base::h(float) " << x << endl; } }; class Derived : public Base { public: virtual void f(float x){ cout << "Derived::f(float) " << x << endl; } void g(int x){ cout << "Derived::g(int) " << x << endl; } void h(float x){ cout << "Derived::h(float) " << x << endl; } }; 以上實現了成員函式的過載、覆蓋和隱藏
(1)函式Derived::f(float)覆蓋了Base::f(float)。 (2)函式Derived::g(int)隱藏了Base::g(float),而不是過載。 (3)函式Derived::h(float)隱藏了Base::h(float),而不是覆蓋。
據作者考察,很多C++程式設計師沒有意識到有“隱藏”這回事。由於認識不夠深刻,“隱 藏”的發生可謂神出鬼沒,常常產生令人迷惑的結果。 示例(b)中,bp 和dp 指向同一地址,按理說執行結果應該是相同的,可事 實並非這樣。 void main(void) { Derived d; Base *pb = &d; Derived *pd = &d; // Good : behavior depends solely on type of the object pb->f(3.14f); // Derived::f(float) 3.14 pd->f(3.14f); // Derived::f(float) 3.14 // Bad : behavior depends on type of the pointer pb->g(3.14f); // Base::g(float) 3.14 pd->g(3.14f); // Derived::g(int) 3 (surprise!) // Bad : behavior depends on type of the pointer pb->h(3.14f); // Base::h(float) 3.14 (surprise!) pd->h(3.14f); // Derived::h(float) 3.14 } 示例(b) 過載、覆蓋和隱藏的比較 8.2.3 擺脫隱藏
3 擺脫隱藏 隱藏規則引起了不少麻煩。示例8-2-3 程式中,語句pd->f(10)的本意是想呼叫函 數Base::f(int),但是Base::f(int)不幸被Derived::f(char *)隱藏了。由於數字10 不能被隱式地轉化為字串,所以在編譯時出錯。 class Base { public: void f(int x); }; class Derived : public Base { public: void f(char *str); }; void Test(void) { Derived *pd = new Derived; pd->f(10); // error } 示例由於隱藏而導致錯誤 從示例 看來,隱藏規則似乎很愚蠢。但是隱藏規則至少有兩個存在的理由: