|
C++ 和 Delphi 的函數(shù)覆蓋(Override)與重載(overload)
Spacesoft【暗夜狂沙】
在面向?qū)ο缶幊讨,?dāng)子類繼承了來自基類的函數(shù)后,子類有可能需要對其中的一些函數(shù)作出與基類不同處理,比如:
class CHuman { public: void SayMyName()//打印出對象的姓名 { cout << "Hi, I am a human" << endl; } };
那么很明顯,假如他的子類有一個同名、同參數(shù)和返回值(一句話,一摸一樣)的函數(shù)SayMyName,它會調(diào)用哪個函數(shù)呢?比如現(xiàn)在有一個class CMark
class CMark: public CHuman { public: void SayMyName() { cout << "Hi, I am mark" << endl; } };
那么我們要問,下面的程序段:
CHuman *pH = new CMark; if (pH) pH->SayMyName(); else cout << "cast error! " << endl;
delete pH; pH = NULL;
要打印出來的,真的是我們想要的Hi, I am mark 嗎?
不是。它輸出了Hi, I am a human。這很糟糕,當(dāng)我們指著一個人要他說出自己的名字的時候,他卻告訴我們他“是一個人”,而不是說出自己的名字。出現(xiàn)這樣的問題原因在于,用基類的指針指向公有派生類,可以訪問派生類從基類中繼承的成員函數(shù)。但如果派生類中也有同名的函數(shù),則結(jié)果仍然是訪問基類的同名函數(shù),而不是派生類本身的函數(shù)。而事實上,我們希望的是由一個對象的真實類型來決定到底該調(diào)用這些同名函數(shù)中的哪一個,就是說,這樣的決議是動態(tài)(Dynamic)的;蛘呶覀兛梢哉f,我們希望當(dāng)一個對象是子類型時,它的同名函數(shù)在子類中的實現(xiàn)覆蓋(override)掉基類的實現(xiàn)。
我們先從C++對這個問題的處理說起。
這是C++中比較典型的多態(tài)的例子,C++用虛函數(shù)來實現(xiàn)這樣的多態(tài)。具體點說,就是使用virtual 關(guān)鍵字來將函數(shù)說明成虛函數(shù),在上一個例子中就是應(yīng)該聲明成:
class CHuman { public: virtual void SayMyName()//打印出對象的姓名 { cout << "Hi, I am a human" << endl; } };
這樣,其他的代碼還是那個老樣子,但是我們的CMark 已經(jīng)知道怎么說自己的名字了。CMark 的SayMyName()函數(shù)是否加了virtual 關(guān)鍵字的說明并沒有關(guān)系,因為根據(jù)C++語法的規(guī)定,因為它覆蓋了CHuman 的同名函數(shù),它自己也就成為virtual 的了。至于為什么一個virtual 關(guān)鍵字有那么神奇的效果呢?C++ FAQ Lite 對此是這樣說明的: 在C++中,“虛成員函數(shù)是動態(tài)確定的(在運行時)。也就是說,成員函數(shù)(在運行時)被動態(tài)地選擇,該選擇基于對象的類型,而不是指向該對象的指針/引用的類型”。于是我們的pH就發(fā)現(xiàn)自己其實指向的是一個CMark類型的對象,而不是自己的類型所聲明的CHuman,所以它聰明的調(diào)用了CMark的SayMyName。
而Delphi 就是用override 關(guān)鍵字來說明函數(shù)覆蓋的。被覆蓋的函數(shù)必須是虛(virtual)的,或者是動態(tài)(dynamic)的,也就是說該函數(shù)在聲明時應(yīng)該包含這兩個指示字中的一個,比如:
procedure Draw; virtual;
在需要覆蓋的時候,只需要在子類中用override 指示字重新聲明一下就可以了。
procedure Draw; override;
在語法上來說,聲明為 virtual和 dynamic是等價的。它們的差別在于,前者在實現(xiàn)上對速度進行了優(yōu)化,而后者對代碼大小進行了優(yōu)化。
假如基類和子類都含有同一個函數(shù)名和參數(shù),并且在子類中不加override 指示字呢?這在語法上也是正確的。這意味著子類的函數(shù)同名實現(xiàn)把基類的實現(xiàn)隱藏(hide)掉了,盡管這二者在派生類中都存在。那么就回到了本文開頭的第一個例子說明的情況:當(dāng)我們指著一個人要他說出自己的名字的時候,他卻告訴我們他“是一個人”,而不是說出自己的名字。
值得注意的是,與我們在C++ 中常常不加區(qū)分的把覆蓋一個函數(shù)和重載一個函數(shù)通稱為重載不同,在Delphi 中,只有重載(overload) 才是我們平時所說的重載,被重載的函數(shù)依然存在,依靠參數(shù)來決定到底調(diào)用那個實現(xiàn)。當(dāng)然,當(dāng)overload掉的函數(shù)和基類的函數(shù)參數(shù)相同時,基類的實現(xiàn)就被hide掉了,就像上面提到的一樣。而覆蓋(override)則是把讓被覆蓋的函數(shù)不可見了,確確實實的"覆蓋"掉了,原來的實現(xiàn)就不見了。基于這樣的原因,許多文章甚至一些書都錯誤的把override翻譯成重載,筆者認為并不合適。
|