(轉自 http://blog.csdn.net/xxxluozhen/article/details/4030946)
1、方法的覆蓋是子類和父類之間的關系,是垂直關系;方法的重載是同一個類中方法之間的關系,是水平關系。
2、覆蓋只能由一個方法,或只能由一對方法產生關系;方法的重載是多個方法之間的關系。
3、覆蓋要求參數列表相同;重載要求參數列表不同。
4、覆蓋關系中,調用那個方法體,是根據對象的類型(對象對應存儲空間類型)來決定;重載關系,是根據調用時的實參表與形參表來選擇方法體的。
override可以翻譯為覆蓋,從字面就可以知道,它是覆蓋了一個方法並且對其重寫,以求達到不同的作用。對我們來說最熟悉的覆蓋就是對接口方法的實現,在接口中一般只是對方法進行了聲明,而我們在實現時,就需要實現接口聲明的所有方法。除了這個典型的用法以外,我們在繼承中也可能會在子類覆蓋父類中的方法。在覆蓋要注意以下的幾點:
1、覆蓋的方法的標志必須要和被覆蓋的方法的標志完全匹配,才能達到覆蓋的效果;
2、覆蓋的方法的返回值必須和被覆蓋的方法的返回一致;
3、覆蓋的方法所拋出的異常必須和被覆蓋方法的所拋出的異常一致,或者是其子類;
4、被覆蓋的方法不能為private,否則在其子類中只是新定義了一個方法,並沒有對其進行覆蓋。
overload對我們來說可能比較熟悉,可以翻譯為重載,它是指我們可以定義一些名稱相同的方法,通過定義不同的輸入參數來區分這些方法,然后再調用時,VM就會根據不同的參數樣式,來選擇合適的方法執行。在使用重載要注意以下的幾點:
1、在使用重載時只能通過不同的參數樣式。例如,不同的參數類型,不同的參數個數,不同的參數順序(當然,同一方法內的幾個參數類型必須不一樣,例如可以是fun(int, float), 但是不能為fun(int, int));
2、不能通過訪問權限、返回類型、拋出的異常進行重載;
3、方法的異常類型和數目不會對重載造成影響;
overload編譯時的多態
override運行時的多態
面向對象程序設計中的另外一個重要概念是多態性。在運行時,可以通過指向基類的指針,來調用實現派生類中的方法。可以把一組對象放到一個數組中,然后調用它們的方法,在這種場合下,多態性作
用就體現出來了,這些對象不必是相同類型的對象。當然,如果它們都繼承自某個類,你可以把這些派生類,都放到一個數組中。如果這些對象都有同名方法,就可以調用每個對象的同名方法。
同一操作作用於不同的對象,可以有不同的解釋,產生不同的執行結果,這就是多態性。多態性通過派生類重載基類中的虛函數型方法來實現。
在面向對象的系統中,多態性是一個非常重要的概念,它允許客戶對一個對象進行操作,由對象來完成一系列的動作,具體實現哪個動作、如何實現由系統負責解釋。
“多態性”一詞最早用於生物學,指同一種族的生物體具有相同的特性。在C#中,多態性的定義是:同一操作作用於不同的類的實例,不同的類將進行不同的解釋,最后產生不同的執行結果。C#支持兩種類型的多態性:
● 編譯時的多態性
編譯時的多態性是通過重載來實現的。對於非虛的成員來說,系統在編譯時,根據傳遞的參數、返回的類型等信息決定實現何種操作。
● 運行時的多態性
運行時的多態性就是指直到系統運行時,才根據實際情況決定實現何種操作。C#中,運行時的多態性通過虛成員實現。
編譯時的多態性為我們提供了運行速度快的特點,而運行時的多態性則帶來了高度靈活和抽象的特點。
舉個簡單的例子:
void test(CBase *pBase)
{
pBase->VirtualFun();
}
這段程序編譯的時刻並不知道運行時刻要調用那個子類的函數,所以編譯的時刻並不會選擇跳轉到那個函數去!如果不是虛函數,那么跳轉的偽匯編代碼應該是call VirtuallFun!但當是虛函數的時候,就不能這樣了,而是變成了call pBase->虛函數表里的一個變量,不同的子類在這個變量含有不同的函數地址,這就是所謂的運行時刻了。但事實上 pBase->虛函數表里的一個變量 也是在編譯時刻就產生的的,它是固定的。 所以運行時刻,還是編譯時刻事實上也並不嚴密,重要的還是理解它的實質!
虛函數只是一個函數指針表,具體調用哪個類的相關函數,要看運行是,對象指針或引用所指的真實類型,由於一個基類的指針或引用可以指向不同的派生類,所以,當用基類指針或引用調用虛函數時,結果是由運行時對象的類型決定的
#################################################################################
“overload”翻譯過來就是:超載,過載,重載,超出標准負荷;“override”翻譯過來是:重置,覆蓋,使原來的失去效果。
先來說說重載的含義,在日常生活中我們經常要清洗一些東西,比如洗車、洗衣服。盡管我們說話的時候並沒有明確地說用洗車的方式來洗車,或者用洗衣服的方式來洗一件衣服,但是誰也不會用洗衣服的方式來洗一輛車,否則等洗完時車早就散架了。我們並不要那么明確地指出來就心知肚明,這就有重載的意思了。在同一可訪問區內被聲名的幾個具有不同參數列的(參數的類型、個數、順序不同)同名函數,程序會根據不同的參數列來確定具體調用哪個函數,這種機制叫重載,重載不關心函數的返回值類型。這里,“重載”的“重”的意思不同於“輕重”的“重”,它是“重復”、“重疊”的意思。例如在同一可訪問區內有:
① double calculate(double);
② double calculate(double,double);
③ double calculate(double, int);
④ double calculate(int, double);
⑤ double calculate(int);
⑥ float calculate(float);
⑦ float calculate(double);
六個同名函數calculate,①②③④⑤⑥中任兩個均構成重載,⑥和⑦也能構成重載,而①和⑦卻不能構成重載,因為①和⑦的參數相同。
覆蓋是指派生類中存在重新定義的函數,其函數名、參數列、返回值類型必須同父類中的相對應被覆蓋的函數嚴格一致,覆蓋函數和被覆蓋函數只有函數體(花括號中的部分)不同,當派生類對象調用子類中該同名函數時會自動調用子類中的覆蓋版本,而不是父類中的被覆蓋函數版本,這種機制就叫做覆蓋。
下面我們從成員函數的角度來講述重載和覆蓋的區別。
成員函數被重載的特征有:
1) 相同的范圍(在同一個類中);
2) 函數名字相同;
3) 參數不同;
4) virtual關鍵字可有可無。
覆蓋的特征有:
1) 不同的范圍(分別位於派生類與基類);
2) 函數名字相同;
3) 參數相同;
4) 基類函數必須有virtual關鍵字。
比如,在下面的程序中:
#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)
}
函數Base::f(int)與Base::f(float)相互重載,而Base::g(void)被Derived::g(void)覆蓋。
隱藏是指派生類的函數屏蔽了與其同名的基類函數,規則如下:
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),而不是覆蓋。
看完前面的示例,可能大家還沒明白隱藏與覆蓋到底有什么區別,因為我們前面都是講的表面現象,怎樣的實現方式,屬於什么情況。下面我們就要分析覆蓋與隱藏在應用中到底有什么不同之處。在下面的程序中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
// Bad : behavior depends on type of the pointer
pb->h(3.14f); //運行結果: Base::h(float) 3.14
pd->h(3.14f); //運行結果: Derived::h(float) 3.14
}
請大家注意,f()函數屬於覆蓋,而g()與h()屬於隱藏。從上面的運行結果,我們可以注意到在覆蓋中,用基類指針和派生類指針調用函數f()時,系統都是執行的派生類函數f(),而非基類的f(),這樣實際上就是完成的“接口”功能。而在隱藏方式中,用基類指針和派生類指針調用函數f()時,系統會進行區分,基類指針調用時,系統執行基類的f(),而派生類指針調用時,系統“隱藏”了基類的f(),執行派生類的f(),這也就是“隱藏”的由來。