C++ 和 Delphi 的函數覆蓋(Override)與重載(overload

C++ 和 Delphi 的函數覆蓋(Override)與重載(overload)

Spacesoft【暗夜狂沙】

在面向對象編程中,當子類繼續了來自基類的函數後,子類有可能需要對其中的一些函數作出與基類不同處理,比如:

class CHuman

{

public:

void SayMyName()//打印出對象的姓名

{

cout << "Hi, I am a human" << endl;

}

};

那麽很明顯,假如他的子類有一個同名、同參數和返回值(一句話,一摸一樣)的函數SayMyName,它會調用哪個函數呢?比如現在有一個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。這很糟糕,當我們指著一個人要他說出自己的名字的時候,他卻告訴我們他“是一個人”,而不是說出自己的名字。出現這樣的問題原因在于,用基類的指針指向公有派生類,可以訪問派生類從基類中繼續的成員函數。但假如派生類中也有同名的函數,則結果仍然是訪問基類的同名函數,而不是派生類本身的函數。而事實上,我們希望的是由一個對象的真實類型來決定到底該調用這些同名函數中的哪一個,就是說,這樣的決議是動態(Dynamic)的。或者我們可以說,我們希望當一個對象是子類型時,它的同名函數在子類中的實現覆蓋(override)掉基類的實現。

我們先從C++對這個問題的處理說起。

這是C++中比較典型的多態的例子,C++用虛函數來實現這樣的多態。具體點說,就是使用virtual 要害字來將函數說明成虛函數,在上一個例子中就是應該聲明成:

class CHuman

{

public:

virtual void SayMyName()//打印出對象的姓名

{

cout << "Hi, I am a human" << endl;

}

};

這樣,其他的代碼還是那個老樣子,但是我們的CMark 已經知道怎麽說自己的名字了。CMark 的SayMyName()函數是否加了virtual 要害字的說明並沒有關系,因爲根據C++語法的規定,因爲它覆蓋了CHuman 的同名函數,它自己也就成爲virtual 的了。至于爲什麽一個virtual 要害字有那麽神奇的效果呢?C++ FAQ Lite 對此是這樣說明的: 在C++中,“虛成員函數是動態確定的(在運行時)。也就是說,成員函數(在運行時)被動態地選擇,該選擇基于對象的類型,而不是指向該對象的指針/引用的類型”。于是我們的pH就發現自己其實指向的是一個CMark類型的對象,而不是自己的類型所聲明的CHuman,所以它聰明的調用了CMark的SayMyName。

而Delphi 就是用override 要害字來說明函數覆蓋的。被覆蓋的函數必須是虛(virtual)的,或者是動態(dynamic)的,也就是說該函數在聲明時應該包含這兩個指示字中的一個,比如:

procedure Draw; virtual;

在需要覆蓋的時候,只需要在子類中用override 指示字重新聲明一下就可以了。

procedure Draw; override;

在語法上來說,聲明爲 virtual和 dynamic是等價的。它們的差別在于,前者在實現上對速度進行了優化,而後者對代碼大小進行了優化。

假如基類和子類都含有同一個函數名和參數,並且在子類中不加override 指示字呢?這在語法上也是正確的。這意味著子類的函數同名實現把基類的實現隱藏(hide)掉了,盡管這二者在派生類中都存在。那麽就回到了本文開頭的第一個例子說明的情況:當我們指著一個人要他說出自己的名字的時候,他卻告訴我們他“是一個人”,而不是說出自己的名字。

值得注重的是,與我們在C++ 中經常不加區分的把覆蓋一個函數和重載一個函數通稱爲重載不同,在Delphi 中,只有重載(overload) 才是我們平時所說的重載,被重載的函數依然存在,依靠參數來決定到底調用那個實現。當然,當overload掉的函數和基類的函數參數相同時,基類的實現就被hide掉了,就像上面提到的一樣。而覆蓋(override)則是把讓被覆蓋的函數不可見了,確確實實的"覆蓋"掉了,原來的實現就不見了。基于這樣的原因,許多文章甚至一些書都錯誤的把override翻譯成重載,筆者認爲並不合適。

C++ 和 Delphi 的函數覆蓋(Override)與重載(overload)
C++ 和 Delphi 的函數覆蓋(Override)與重載(overload)Spacesoft【暗夜狂沙】在面向對象編程中,當子類繼承了來自基類的函數後,子類有可能需要對其中的一些函數作出與基類不同處理,比如:class CHuman{public: v...查看完整版>>C++ 和 Delphi 的函數覆蓋(Override)與重載(overload)
 
關于C#和C++的重載(Overload)、隱藏(Hide)、覆蓋(Override)幾個區別比較和得出的疑問
  這裏只談函數(方法)重載,C#獨有的屬性重載不在討論之內。  1. Overload: 基本上一致,C#多出了ref、out相關的,C++多出了const(指針和引用時)相關的。  2. Hide: C#引入new; C++一般情況下類...查看完整版>>關于C#和C++的重載(Overload)、隱藏(Hide)、覆蓋(Override)幾個區別比較和得出的疑問
 
談談重載(overload)覆蓋(override)與隱藏
這三個概念都是與OO中的多態有關系的。如果單是區別重載與覆蓋這兩個概念是比較容易的,但是隱藏這一概念卻使問題變得有點複雜了,下面說說它們的區別吧。 重載是指不同的函數使用相同的函數名,但是函數的參數...查看完整版>>談談重載(overload)覆蓋(override)與隱藏
 
[C++基礎]重載、覆蓋、多態與函數隱藏(4)
(續上) [C++基礎]重載、覆蓋、多態與函數隱藏 小結: 重載overload是根據函數的參數列表來選擇要調用的函數版本,而多態是根據運行時對象的實際類型來選擇要調用的虛virtual函數版本...查看完整版>>[C++基礎]重載、覆蓋、多態與函數隱藏(4)
 
[C++基礎]重載、覆蓋、多態與函數隱藏(3)
(續上) [C++基礎]重載、覆蓋、多態與函數隱藏例8-2#include <iostream> using namespace std; class Base{public: virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl; }}; clas...查看完整版>>[C++基礎]重載、覆蓋、多態與函數隱藏(3)
 
[C++基礎]重載、覆蓋、多態與函數隱藏(2)
(續上)[C++基礎]重載、覆蓋、多態與函數隱藏 好了,我們先來一個小小的總結重載與覆蓋兩者之間的特征 重載overload的特征:n 相同的範圍(在同一個類中);n 函數名相同參數不同;n virtu...查看完整版>>[C++基礎]重載、覆蓋、多態與函數隱藏(2)
 
[C++基礎]重載、覆蓋、多態與函數隱藏(1)
重載、覆蓋、多態與函數隱藏 經常看到C++的一些初學者對于重載、覆蓋、多態與函數隱藏的模糊理解。在這裏寫一點自己的見解,希望能夠C++初學者解惑。 要弄清楚重載、覆蓋、多態與函數隱藏之間的複雜且微妙關系之前,...查看完整版>>[C++基礎]重載、覆蓋、多態與函數隱藏(1)
 
新手入門:C++中的函數重載
  函數重載是用來描述同名函數具有相同或者相似功能,但數據類型或者是參數不同的函數治理操作的稱呼。   我們來...查看完整版>>新手入門:C++中的函數重載
 
[vcl源碼精華]之[borland工程師如何保證父類(TStream)的兩個overload的seek,至少有一個必須被override]
TStream 使用了下面的代碼,強制子類必須implement兩個seek方法中至少一個classes.pas 單元4904行@Delphi7首先說明TStream有兩個seek方法function Seek(Offset: Longint; Origin: Word): Longint; overload; virtual;...查看完整版>>[vcl源碼精華]之[borland工程師如何保證父類(TStream)的兩個overload的seek,至少有一個必須被override]
 
 
回到王朝網路移動版首頁