天天看點

C#重載與覆寫的差別

C#重載與覆寫的差別

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(),這也就是“隐藏”的由來。

附上原文連結 點選打開連結