我創建了一個類,基類中有虛擬重載函數。我想在派生類中改寫基類中的虛擬重載函數。代碼如下:
#include <iostream.h>
class B {
private:
int nNumber;
public:
virtual void test() {
cout << "B::test()\n"; }
virtual void test(int x) {
nNumber = x; // 將傳入的參數賦值給私有成員
cout << "B::test(int x)\n";
}
};
class D : public B {
public:
//test(void) 隱藏 B::test(int)
virtual void test() {
cout << "D::test()\n";
}
};
void main(int argc, char* argv[])
{
D d; // 派生類的實例
d.test(); // OK
d.test(17); // 產生編譯錯誤的代碼行
}
基類中有兩個重載的test函數。當我在派生類中改寫其中的一個函數時,我覺得另外一個應該在派生類中繼承,但編譯時出現一下錯誤信息:
... C2660 : ''test'' :function does not take 1 parameters
在不放棄多態行為的情況下,我能不能同時重載和改寫某個函數?
許多C++程序員都對這個問題感到困惑,為了解開這個惑點,有兩個概念你必須要了然於心。一個是重載,一個是名字空間。從重載的視角看,你可能覺得C++應該按照你所想象的方式工作;也就是如果派生類只修改其中的一個重載函數的行為,那麼另外一個應該按照通常的方式被繼承。但從名字空間的角度看,當C++試圖解決某個符號名時,它以由近到遠的順序進行搜索,它首先找本地變量,然後是類成員名,接著是基類名,全局變量……。一旦編譯器解析了名字,它便終止解析過程。如果在不同的名字空間范圍碰到同一個符號名,那麼它便沒有了主意。在你的代碼中,一旦編譯器確定名字"test"出現在類D中,那麼它的名字空間就為類D。不論你調用不帶參數的test也好,還是調用帶一個整型參數的test(17)也好,編譯器都是遵循准這個規則來解決名字問題。對於有整型參數的test(int x)函數,一旦編譯器確定函數名test生存在類D中,它便終止范圍探查,然後查找與參數匹配的函數。由於它沒有在類D中找到帶整型參數的函數,所以報錯。此時編譯器認為函數D::test(void)隱藏了函數B::test(int)。你可能會問,為什麼要按這種方式處理?問得好。想找到答案,請參考Bjarne Stroustrup 的C++注釋參考手冊(Section 13.1)。簡單地說,在類層次深處搜索與某個簽名匹配的重載函數比不搜索更可能導致混亂。
那麼如何解決上面出現的問題呢?很容易。只要在派生類中創建另一個顯式調用基類的test函數即可,如:
class D : public B {
public:
virtual void test(int x) { B::test(x); }
……
};
這樣,D就有了兩個test函數,問題也解決了。 其實這個問題還有另外一個解法:那就是在派生類中使用"using"關鍵字。如:
class D : public B {
public:
using B::test;
// 改寫test
virtual void test() {
cout << "D::test()\n";
}
};
這個關鍵字將B::test帶入類D中。它的優點是當類B擴展更多的test重載函數時,你不用再去重寫類D的代碼。這樣省了很多事。只要你使用using關鍵字,那麼就意味著所有重載的 B::test函數都進入類D並且得到繼承。這種特性可能對於某些人來說是優點,而對於另外一些人來說是缺點,依賴於你要做什麼事情。如果你想要向類D的用戶隱藏某些test函數,則可以用第一種方法(顯式調用基類),或者讓你想要隱藏的函數成為private 或 protected類型。如果你讀過Scott的書《Effective C++》,還可以用第二種方法利用using關鍵字巧妙地保證基類的私隱。class B {
public:
func1();
func2(double d);
virtual test();
virtual test(int x);
};
// D 從B中秘密派生
class D : private B {
public:
// 讓所有 B::test 函數為 public類型
using B::test;
};
顯然,當希望完全隱藏類B時,這個方法很有用,但如果想要公開B中的個別函數,則這個方法就不是那麼好了。