c++中被忽視的隱藏

原文:http://blog.163.com/dmg_123456/blog/static/56705063200992395226772/


稍微懂得點oop的人都知道重載,那是多態性的重要體現!可是在c++中你能分清成員函數的重載、覆蓋嗎?這個好像也不難,重載存在與同一個類中,而覆蓋存在於派生類於基類中!可是如果再加上隱藏呢?說實話,以前我從來沒有聽說過這個概念!也不知道自己曾經捏造的程序,出了多少問題!看看林銳在《高質量 c++編程指南》中的解釋。


    成員函數的重載、覆蓋(override)與隱藏很容易混淆,C++程序員必須要搞清楚概念,否則錯誤將防不勝防。


 

8.2.1 重載與覆蓋

    成員函數被重載的特徵:

(1)相同的範圍(在同一個類中);

(2)函數名字相同;

(3)參數不同;

(4)virtual關鍵字可有可無。

    覆蓋是指派生類函數覆蓋基類函數,特徵是:

(1)不同的範圍(分別位於派生類與基類);

(2)函數名字相同;

(3)參數相同;

(4)基類函數必須有virtual關鍵字。

    示例8-2-1中,函數Base::f(int)與Base::f(float)相互重載,而Base::g(void)被Derived::g(void)覆蓋。


 


 

#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)


 

示例8-2-1成員函數的重載和覆蓋

   

8.2.2 令人迷惑的隱藏規則

    本來僅僅區別重載與覆蓋並不算困難,但是C++的隱藏規則使問題複雜性陡然增加。這裏“隱藏”是指派生類的函數屏蔽了與其同名的基類函數,規則如下:

(1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。

(2)如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。

    示例程序8-2-2(a)中:

(1)函數Derived::f(float)覆蓋了Base::f(float)。

(2)函數Derived::g(int)隱藏了Base::g(float),而不是重載。

(3)函數Derived::h(float)隱藏了Base::h(float),而不是覆蓋。


 


 

#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; }

}; 
 

示例8-2-2(a)成員函數的重載、覆蓋和隱藏


 


 

    據作者考察,很多C++程序員沒有意識到有“隱藏”這回事。由於認識不夠深刻,“隱藏”的發生可謂神出鬼沒,常常產生令人迷惑的結果。

示例8-2-2(b)中,pb和pd指向同一地址,按理說運行結果應該是相同的,可事實並非這樣。


 


 

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        (surprise!)


 


 

// Bad : behavior depends on type of the pointer

pb->h(3.14f); // Base::h(float) 3.14      (surprise!)

pd->h(3.14f); // Derived::h(float) 3.14


 

示例8-2-2(b) 重載、覆蓋和隱藏的比較

8.2.3 擺脫隱藏

    隱藏規則引起了不少麻煩。示例8-2-3程序中,語句pd->f(10)的本意是想調用函數Base::f(int),但是Base::f(int)不幸被Derived::f(char *)隱藏了。由於數字10不能被隱式地轉化爲字符串,所以在編譯時出錯。


 


 

class Base

{

public:

void f(int x);

}; 
 
class Derived : public Base

{

public:

void f(char *str);

}; 
 
void Test(void)

{

Derived *pd = new Derived;

pd->f(10);    // error


 

示例8-2-3 由於隱藏而導致錯誤


 


 

    從示例8-2-3看來,隱藏規則似乎很愚蠢。但是隱藏規則至少有兩個存在的理由:

u       寫語句pd->f(10)的人可能真的想調用Derived::f(char *)函數,只是他誤將參數寫錯了。有了隱藏規則,編譯器就可以明確指出錯誤,這未必不是好事。否則,編譯器會靜悄悄地將錯就錯,程序員將很難發現這個錯誤,流下禍根。

u       假如類Derived有多個基類(多重繼承),有時搞不清楚哪些基類定義了函數f。如果沒有隱藏規則,那麼pd->f(10)可能會調用一個出乎意料的基類函數f。儘管隱藏規則看起來不怎麼有道理,但它的確能消滅這些意外。


 


 

示例8-2-3中,如果語句pd->f(10)一定要調用函數Base::f(int),那麼將類Derived修改爲如下即可。

class Derived : public Base

{

public:

void f(char *str);

void f(int x) { Base::f(x); }

};

2

重載與覆蓋的區別
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,①②③④⑤⑥中任兩個均構成重載,⑥和⑦也能構成重載,而①和⑦卻不能構成重載,因爲①和⑦的參數相同。

覆蓋是指派生類中存在重新定義的函數,其函數名、參數列、返回值類型必須同父類中的相對應被覆蓋的函數嚴格一致,覆蓋函數和被覆蓋函數只有函數體(花括號中的部分)不同,當派生類對象調用子類中該同名函數時會自動調用子類中的覆蓋版本,而不是父類中的被覆蓋函數版本,這種機制就叫做覆蓋。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章