重載,覆蓋,隱藏和多態

成員函數被重載的特徵
(1)相同的範圍(在同一個類中);
(2)函數名字相同;
(3)參數不同(類型或者個數不同);
(4)virtual 關鍵字可有可無。
覆蓋是指派生類函數覆蓋基類函數,特徵是
(1)不同的範圍(分別位於派生類與基類);
(2)函數名字相同;
(3)參數相同;
(4)基類函數必須有virtual 關鍵字。
“隱藏”是指派生類的函數屏蔽了與其同名的基類函數,規則如下
(1)如果派生類的函數與基類的函數同名,但是參數不同。此時,不論有無virtual關鍵字,基類的函數將被隱藏(注意別與重載混淆)。
(2)如果派生類的函數與基類的函數同名,並且參數也相同,但是基類函數沒有virtual 關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)
3種情況怎麼執行:
1。重載:看參數
2。隱藏:用什麼就調用什麼

3。覆蓋:調用派生類

#include<iostream>
#include<string>
using namespace std;

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; }
void j(int x){cout<<"Base::j(int)"<<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; }
void j(float x){cout<<"Derived::j(float)"<<x<<endl;}
};

void main()
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
//覆蓋情況的方法f,pb不能找到自己的f方法
pb->f(3.14f); // Derived::f(float) 3.14
pd->f(3.14f); // Derived::f(float) 3.14
//隱藏情況的方法g,pb可以找到自己的g方法
pb->g(3.14f); // Base::g(float) 3.14 (運用基類類型的指針變量來實現是可以的)
pd->g(3.14f); // Derived::g(int) 3 (受到自動類型轉換的影響)
//隱藏情況的方法h,pb可以找到自己的h方法
pb->h(3.14f); // Base::h(float) 3.14
pd->h(3.14f); // Derived::h(float) 3.14
//隱藏情況的方法j,pb可以找到自己的j方法
pb->j(3.14f);//Base::j(int) 3(受到自動類型轉換的影響)
pd->j(3.14f);//Derived::j(float)3.14

}

接下來的幾個例子具體說明一下什麼叫隱藏

例1

#include <iostream>

using namespace std;

class Basic{

public:

        void fun(){cout << "Base::fun()" << endl;}//overload

        void fun(int i){cout << "Base::fun(int i)" << endl;}//overload

};

class Derive :public Basic{

public:

        //新的函數版本,基類所有的重載版本都被屏蔽,在這裏,我們稱之爲函數隱藏hide

    //派生類中有基類的同名函數的聲明,則基類中的同名函數不會作爲候選函數,即使基類有不同的參數表的多個版本的重載函數。

        void fun(int i,int j){cout << "Derive::fun(int i,int j)" << endl;}

        void fun2(){cout << "Derive::fun2()" << endl;}

};

int main()

{

        Derive d;

        d.fun(1,2);

        //下面一句錯誤,故屏蔽掉(不使用指針是無法訪問基類被隱藏的函數的)

        //d.fun();error C2660: 'fun' : function does not take 0 parameters

        return 0;

}

//函數名相同但是參數不同,所以是基類的函數與派生類中同名函數被隱藏。

例2

#include <iostream>

using namespace std;

class Basic{

public:

        void fun(){cout << "Base::fun()" << endl;}//overload

        void fun(int i){cout << "Base::fun(int i)" << endl;}//overload

};

class Derive :public Basic{

public:

        void fun(){cout << "Derive::fun()" << endl;}

        void fun2(){cout << "Derive::fun2()" << endl;}

};

int main()

{

        Derive d;

        d.fun();

        //下面一句錯誤,故屏蔽掉

        //d.fun(1);error C2660: 'fun' : function does not take 1 parameters

        return 0;

}

 //覆蓋override基類的其中一個函數版本,同樣基類所有的重載版本都被隱藏hide

//派生類中有基類的同名函數的聲明,則基類中的同名函數不會作爲候選函數,即使基類有不同的參數表的多個版本的重載函數

例3

#include <iostream>

using namespace std;

class Basic{

public:

        void fun(){cout << "Base::fun()" << endl;}//overload

        void fun(int i){cout << "Base::fun(int i)" << endl;}//overload

};

class Derive :public Basic{

public:

        using Basic::fun;

        void fun(){cout << "Derive::fun()" << endl;}

        void fun2(){cout << "Derive::fun2()" << endl;}

};

int main()

{

        Derive d;

        d.fun();//正確

        d.fun(1);//正確

        return 0;

}

/*

輸出結果

Derive::fun()

Base::fun(int i)

Press any key to continue

*/

//hide 和 顯式聲明基類名字空間作用域

例4

#include <iostream>

using namespace std;

class Basic{

public:

        void fun(){cout << "Base::fun()" << endl;}//overload

        void fun(int i){cout << "Base::fun(int i)" << endl;}//overload

};

class Derive :public Basic{

public:

        using Basic::fun;

        void fun(int i,int j){cout << "Derive::fun(int i,int j)" << endl;}

        void fun2(){cout << "Derive::fun2()" << endl;}

};

int main()

{

        Derive d;

        d.fun();//正確

        d.fun(1);//正確

        d.fun(1,2);//正確

        return 0;

}

/*

輸出結果

Base::fun()

Base::fun(int i)

Derive::fun(int i,int j)

Press any key to continue

*/

//同例3

        如果基類有某個函數的多個重載(overload)版本,而你在派生類中重寫(override)了基類中的一個或多個函數版本,或是在派生類中重新添加了新的函數版本(函數名相同,參數不同),則所有基類的重載版本都被屏蔽,在這裏我們稱之爲隱藏hide。所以,在一般情況下,你想在派生類中使用新的函數版本又想使用基類的函數版本時,你應該在派生類中重寫基類中的所有重載版本。你若是不想重寫基類的重載的函數版本,則你應該使用例3或例4方式,顯式聲明基類名字空間作用域。

        事實上,C++編譯器認爲,相同函數名不同參數的函數之間根本沒有什麼關係,它們根本就是兩個毫不相關的函數。只是C++語言爲了模擬現實世界,爲了讓程序員更直觀的思維處理現實世界中的問題,才引入了重載和覆蓋的概念。重載是在相同名字空間作用域下,而覆蓋則是在不同的名字空間作用域下,比如基類和派生類即爲兩個不同的名字空間作用域。在繼承過程中,若發生派生類與基類函數同名問題時,便會發生基類函數的隱藏。當然,這裏討論的情況是基類函數前面沒有virtual 關鍵字。在有virtual 關鍵字關鍵字時的情形我們另做討論。

        繼承類重寫了基類的某一函數版本,以產生自己功能的接口。此時C++編繹器認爲,你現在既然要使用派生類的自己重新改寫的接口,那我基類的接口就不提供給你了(當然你可以用顯式聲明名字空間作用域的方法,而不會理會你基類的接口是有重載特性的。若是你要在派生類裏繼續保持重載的特性,那你就自己再給出接口重載的特性吧。所以在派生類裏,只要函數名一樣,基類的函數版本就會被無情地屏蔽。在編繹器中,屏蔽是通過名字空間作用域實現的。

        所以,在派生類中要保持基類的函數重載版本,就應該重寫所有基類的重載版本。重載只在當前類中有效,繼承會失去函數重載的特性。也就是說,要把基類的重載函數放在繼承的派生類裏,就必須重寫。

例5

#include <iostream>

using namespace std;

class Base{

public:

        virtual void fun() { cout << "Base::fun()" << endl; }//overload

    virtual void fun(int i) { cout << "Base::fun(int i)" << endl; }//overload

};

class Derive : public Base{

public:

        void fun() { cout << "Derive::fun()" << endl; }//override

   void fun(int i) { cout << "Derive::fun(int i)" << endl; }//override

        void fun(int i,int j){ cout<< "Derive::fun(int i,int j)" <<endl;}//overload

};

int main()

{

Base *pb = new Derive();

pb->fun();

pb->fun(1);

//下面一句錯誤,故屏蔽掉

//pb->fun(1,2);virtual函數不能進行overload,error C2661: 'fun' : no overloaded function takes 2 parameters

cout << endl;

Derive *pd = new Derive();

pd->fun();

pd->fun(1);

pd->fun(1,2);//overload

delete pb;

delete pd;

return 0;

}

/*

輸出結果

Derive::fun()

Derive::fun(int i)

Derive::fun()

Derive::fun(int i)

Derive::fun(int i,int j)

Press any key to continue

*/

例6-1

#include <iostream>

using namespace std;

class Base{

public:

        virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl; }

};

class Derive : public Base{};

int main()

{

        Base *pb = new Derive();

        pb->fun(1);//Base::fun(int i)

        delete pb;

        return 0;

}

例6-2

#include <iostream>

using namespace std;

class Base{

public:

         virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl; }

};

class Derive : public Base{

public:

    void fun(double d){ cout <<"Derive::fun(double d)"<< endl; }

};

int main()

{

         Base *pb = new Derive();

         pb->fun(1);//Base::fun(int i)

         pb->fun((double)0.01);//Base::fun(int i)

        delete pb;

         return 0;

}

例7-1

#include <iostream>

using namespace std;

class Base{

public:

        virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl; }

};

class Derive : public Base{

public:

        void fun(int i){ cout <<"Derive::fun(int i)"<< endl; }

};

int main()

{

        Base *pb = new Derive();

        pb->fun(1);//Derive::fun(int i)

        delete pb;

        return 0;

}

例7-2

#include <iostream>

using namespace std;

class Base{

public:

         virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl; }

};

class Derive : public Base{

public:

        void fun(int i){ cout <<"Derive::fun(int i)"<< endl; }

         void fun(double d){ cout <<"Derive::fun(double d)"<< endl; }        

};

int main()

{

         Base *pb = new Derive();

         pb->fun(1);//Derive::fun(int i)

                 pb->fun((double)0.01);//Derive::fun(int i)

         delete pb;

         return 0;

}

例8

#include <iostream>

using namespace std;

class Base{

public:

         virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl; }

};

class Derive : public Base{

public:

        void fun(int i){ cout <<"Derive::fun(int i)"<< endl; }

                 void fun(char c){ cout <<"Derive::fun(char c)"<< endl; }        

                 void fun(double d){ cout <<"Derive::fun(double d)"<< endl; }       

};

int main()

{

         Base *pb = new Derive();

         pb->fun(1);//Derive::fun(int i)

                 pb->fun('a');//Derive::fun(int i)

                 pb->fun((double)0.01);//Derive::fun(int i)

                 Derive *pd =new Derive();

         pd->fun(1);//Derive::fun(int i)

         //overload

                 pd->fun('a');//Derive::fun(char c)         

         //overload

                 pd->fun(0.01);//Derive::fun(double d)         

         delete pb;

                 delete pd;

         return 0;

}

例6-1和例7-1很好理解,我把這兩個例子放在這裏,是讓大家作一個比較擺了,也是爲了幫助大家更好的理解:

n          例7-1中,派生類沒有覆蓋基類的虛函數,此時派生類的vtable中的函數指針指向的地址就是基類的虛函數地址。

n          例8-1中,派生類覆蓋了基類的虛函數,此時派生類的vtable中的函數指針指向的地址就是派生類自己的重寫的虛函數地址。

在例7-2和8-2看起來有點怪怪,其實,你按照上面的原則對比一下,答案也是明朗的:

n          例7-2中,我們爲派生類重載了一個函數版本:void fun(double d) 其實,這只是一個障眼法。我們具體來分析一下,基類共有幾個函數,派生類共有幾個函數:

類型

基類

派生類

Vtable部分

void fun(int i)

指向基類版的虛函數void fun(int i)

靜態部分

 

void fun(double d)

我們再來分析一下以下三句代碼

Base *pb = new Derive();

pb->fun(1);//Base::fun(int i)

pb->fun((double)0.01);//Base::fun(int i)

這第一句是關鍵,基類指針指向派生類的對象,我們知道這是多態調用;接下來第二句,運行時基類指針根據運行時對象的類型,發現是派生類對象,所以首先到派生類的vtable中去查找派生類的虛函數版本,發現派生類沒有覆蓋基類的虛函數,派生類的vtable只是作了一個指向基類虛函數地址的一個指向,所以理所當然地去調用基類版本的虛函數。最後一句,程序運行仍然埋頭去找派生類的vtable,發現根本沒有這個版本的虛函數,只好回頭調用自己的僅有一個虛函數。

這裏還值得一提的是:如果此時基類有多個虛函數,此時程序編繹時會提示”調用不明確”。示例如下

#include <iostream>

using namespace std;

class Base{

public:

         virtual void fun(int i){ cout <<"Base::fun(int i)"<< endl; }

                 virtual void fun(char c){ cout <<"Base::fun(char c)"<< endl; }

};

class Derive : public Base{

public:

    void fun(double d){ cout <<"Derive::fun(double d)"<< endl; }

};

int main()

{

         Base *pb = new Derive();

                 pb->fun(0.01);//error C2668: 'fun' : ambiguous call to overloaded function

         delete pb;

         return 0;

}

好了,我們再來分析一下例7-2。

n          例7-2中,我們也爲派生類重載了一個函數版本:void fun(double d) ,同時覆蓋了基類的虛函數,我們再來具體來分析一下,基類共有幾個函數,派生類共有幾個函數:

類型

基類

派生類

Vtable部分

void fun(int i)

void fun(int i)

靜態部分

 

void fun(double d)

從表中我們可以看到,派生類的vtable中函數指針指向的是自己的重寫的虛函數地址。

我們再來分析一下以下三句代碼

Base *pb = new Derive();

pb->fun(1);//Derive::fun(int i)

pb->fun((double)0.01);//Derive::fun(int i)

第一句不必多說了,第二句,理所當然調用派生類的虛函數版本,第三句,嘿,感覺又怪怪的,其實呀,C++程序很笨的了,在運行時,埋頭闖進派生類的vtable表中,隻眼一看,靠,競然沒有想要的版本,真是想不通,基類指針爲什麼不四處轉轉再找找呢?呵呵,原來是眼力有限,基類年紀這麼老了,想必肯定是老花了,它那雙眼睛看得到的僅是自己的非Vtable部分(即靜態部分)和自己要管理的Vtable部分,派生類的void fun(double d)那麼遠,看不到呀!再說了,派生類什麼都要管,難道派生類沒有自己的一點權力嗎?哎,不吵了,各自管自己的吧^_^

唉!你是不是要嘆氣了,基類指針能進行多態調用,但是始終不能進行派生類的重載調用啊(參考例5)~~~

再來看看例8,本例的效果同例5,異曲同工。想必你理解了上面的這些例子後,這個也是小case了。

小結:

        重載overload是根據函數的參數列表來選擇要調用的函數版本,而多態是根據運行時對象的實際類型來選擇要調用的虛virtual函數版本,多態的實現是通過派生類對基類的虛virtual函數進行覆蓋override來實現的,若派生類沒有對基類的虛virtual函數進行覆蓋override的話,則派生類會自動繼承基類的虛virtual函數版本,此時無論基類指針指向的對象是基類型還是派生類型,都會調用基類版本的虛virtual函數;如果派生類對基類的虛virtual函數進行覆蓋override的話,則會在運行時根據對象的實際類型來選擇要調用的虛virtual函數版本,例如基類指針指向的對象類型爲派生類型,則會調用派生類的虛virtual函數版本,從而實現多態。

        使用多態的本意是要我們在基類中聲明函數爲virtual,並且是要在派生類中覆蓋override基類的虛virtual函數版本,注意,此時的函數原型與基類保持一致,即同名同參數類型;如果你在派生類中新添加函數版本,你不能通過基類指針動態調用派生類的新的函數版本,這個新的函數版本只作爲派生類的一個重載版本。還是同一句話,重載只有在當前類中有效,不管你是在基類重載的,還是在派生類中重載的,兩者互不牽連。如果明白這一點的話,在例5、例8中,我們也會對其的輸出結果順利地理解。

        重載是靜態聯編的,多態是動態聯編的。進一步解釋,重載與指針實際指向的對象類型無關,多態與指針實際指向的對象類型相關。若基類的指針調用派生類的重載版本,C++編繹認爲是非法的,C++編繹器只認爲基類指針只能調用基類的重載版本,重載只在當前類的名字空間作用域內有效,繼承會失去重載的特性,當然,若此時的基類指針調用的是一個虛virtual函數,那麼它還會進行動態選擇基類的虛virtual函數版本還是派生類的虛virtual函數版本來進行具體的操作,這是通過基類指針實際指向的對象類型來做決定的,所以說重載與指針實際指向的對象類型無關,多態與指針實際指向的對象類型相關。

    最後闡明一點,虛virtual函數同樣可以進行重載,但是重載只能是在當前自己名字空間作用域內有效(請再次參考例5)。


原文鏈接:http://blog.163.com/hi_qiqiy@126/blog/static/1440667912010111544228731/

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