溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

C++如何實現多態(tài)機制

發(fā)布時間:2020-10-23 16:21:28 來源:億速云 閱讀:161 作者:小新 欄目:編程語言

C++如何實現多態(tài)機制?這個問題可能是我們日常學習或工作經常見到的。希望通過這個問題能讓你收獲頗深。下面是小編給大家?guī)淼膮⒖純热荩屛覀円黄饋砜纯窗桑?/p>

C++ 多態(tài)的實現及原理

C++的多態(tài)性用一句話概括就是:在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運行時將會根據對象的實際類型來調用相應的函數。如果對象類型是派生類,就調用派生類的函數;如果對象類型是基類,就調用基類的函數

1:用virtual關鍵字申明的函數叫做虛函數,虛函數肯定是類的成員函數。

2:存在虛函數的類都有一個一維的虛函數表叫做虛表,類的對象有一個指向虛表開始的虛指針。虛表是和類對應的,虛表指針是和對象對應的。

3:多態(tài)性是一個接口多種實現,是面向對象的核心,分為類的多態(tài)性和函數的多態(tài)性。

4:多態(tài)用虛函數來實現,結合動態(tài)綁定.

5:純虛函數是虛函數再加上 = 0;

6:抽象類是指包括至少一個純虛函數的類。

純虛函數:virtual void fun()=0;即抽象類!必須在子類實現這個函數,即先有名稱,沒有內容,在派生類實現內容。

我們先看個例子

#include <iostream> #include <stdlib.h>using namespace std; 

class Father
{public:    void Face()
    {        cout << "Father's face" << endl;
    }    void Say()
    {        cout << "Father say hello" << endl;
    }
};class Son:public Father
{public:     
    void Say()
    {        cout << "Son say hello" << endl;
    }
};int main()
{
    Son son;
    Father *pFather=&son; // 隱式類型轉換
    pFather->Say();    return 0;
}

輸出的結果為:

Father say hello

我們在main()函數中首先定義了一個Son類的對象son,接著定義了一個指向Father類的指針變量pFather,然后利用該變量調用pFather->Say().估計很多人往往將這種情況和c++的多態(tài)性搞混淆,認為son實際上是Son類的對象,應該是調用Son類的Say,輸出”Son say hello”,然而結果卻不是.

  從編譯的角度來看:

  c++編譯器在編譯的時候,要確定每個對象調用的函數(非虛函數)的地址,這稱為早期綁定,當我們將Son類的對象son的地址賦給pFather時,c++編譯器進行了類型轉換,此時c++編譯器認為變量pFather保存的就是Father對象的地址,當在main函數中執(zhí)行pFather->Say(),調用的當然就是Father對象的Say函數

 從內存角度看

C++如何實現多態(tài)機制

Son類對象的內存模型如上圖

我們構造Son類的對象時,首先要調用Father類的構造函數去構造Father類的對象,然后才調用Son類的構造函數完成自身部分的構造,從而拼接出一個完整的Son類對象。當我們將Son類對象轉換為Father類型時,該對象就被認為是原對象整個內存模型的上半部分,也就是上圖中“Father的對象所占內存”,那么當我們利用類型轉換后的對象指針去調用它的方法時,當然也就是調用它所在的內存中的方法,因此,輸出“Father Say hello”,也就順理成章了。

  正如很多人那么認為,在上面的代碼中,我們知道pFather實際上指向的是Son類的對象,我們希望輸出的結果是son類的Say方法,那么想到達到這種結果,就要用到虛函數了。

  前面輸出的結果是因為編譯器在編譯的時候,就已經確定了對象調用的函數的地址,要解決這個問題就要使用晚綁定,當編譯器使用晚綁定時候,就會在運行時再去確定對象的類型以及正確的調用函數,而要讓編譯器采用晚綁定,就要在基類中聲明函數時使用virtual關鍵字,這樣的函數我們就稱之為虛函數,一旦某個函數在基類中聲明為virtual,那么在所有的派生類中該函數都是virtual,而不需要再顯式地聲明為virtual。

  代碼稍微改動一下,看一下運行結果

#include <iostream> #include <stdlib.h>using namespace std; 

class Father
{public:    void Face()
    {        cout << "Father's face" << endl;
    }    virtual void Say()
    {        cout << "Father say hello" << endl;
    }
};class Son:public Father
{public:     
    void Say()
    {        cout << "Son say hello" << endl;
    }
};int main()
{
    Son son;
    Father *pFather=&son; // 隱式類型轉換
    pFather->Say();    return 0;
}

運行結果:

Son say hello

我們發(fā)現結果是”Son say hello”也就是根據對象的類型調用了正確的函數,那么當我們將Say()聲明為virtual時,背后發(fā)生了什么。

  編譯器在編譯的時候,發(fā)現Father類中有虛函數,此時編譯器會為每個包含虛函數的類創(chuàng)建一個虛表(即 vtable),該表是一個一維數組,在這個數組中存放每個虛函數的地址,

C++如何實現多態(tài)機制

那么如何定位虛表呢?編譯器另外還為每個對象提供了一個虛表指針(即vptr),這個指針指向了對象所屬類的虛表,在程序運行時,根據對象的類型去初始化vptr,從而讓vptr正確的指向了所屬類的虛表,從而在調用虛函數的時候,能夠找到正確的函數,對于第二段代碼程序,由于pFather實際指向的對象類型是Son,因此vptr指向的Son類的vtable,當調用pFather->Son()時,根據虛表中的函數地址找到的就是Son類的Say()函數.

  正是由于每個對象調用的虛函數都是通過虛表指針來索引的,也就決定了虛表指針的正確初始化是非常重要的,換句話說,在虛表指針沒有正確初始化之前,我們不能夠去調用虛函數,那么虛表指針是在什么時候,或者什么地方初始化呢?

  答案是在構造函數中進行虛表的創(chuàng)建和虛表指針的初始化,在構造子類對象時,要先調用父類的構造函數,此時編譯器只“看到了”父類,并不知道后面是否還有繼承者,它初始化父類對象的虛表指針,該虛表指針指向父類的虛表,當執(zhí)行子類的構造函數時,子類對象的虛表指針被初始化,指向自身的虛表。

  總結(基類有虛函數的):

  1:每一個類都有虛表

  2:虛表可以繼承,如果子類沒有重寫虛函數,那么子類虛表中仍然會有該函數的地址,只不過這個地址指向的是基類的虛函數實現,如果基類有3個虛函數,那么基類的虛表中就有三項(虛函數地址),派生類也會虛表,至少有三項,如果重寫了相應的虛函數,那么虛表中的地址就會改變,指向自身的虛函數實現,如果派生類有自己的虛函數,那么虛表中就會添加該項。

  3:派生類的虛表中虛地址的排列順序和基類的虛表中虛函數地址排列順序相同。

  這就是c++中的多態(tài)性,當c++編譯器在編譯的時候,發(fā)現Father類的Say()函數是虛函數,這個時候c++就會采用晚綁定技術,也就是編譯時并不確定具體調用的函數,而是在運行時,依據對象的類型來確認調用的是哪一個函數,這種能力就叫做c++的多態(tài)性,我們沒有在Say()函數前加virtual關鍵字時,c++編譯器就確定了哪個函數被調用,這叫做早期綁定。

  c++的多態(tài)性就是通過晚綁定技術來實現的。

  c++的多態(tài)性用一句話概括就是:在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運行時將會根據對象的實際類型來調用相應的函數,如果對象類型是派生類,就調用派生類的函數,如果對象類型是基類,就調用基類的函數。

  虛函數是在基類中定義的,目的是不確定它的派生類的具體行為,例如:

  定義一個基類:class Animal //動物,它的函數為breathe()

  再定義一個類class Fish //魚。它的函數也為breathe()

  再定義一個類class Sheep //羊,它的函數也為breathe()

將Fish,Sheep定義成Animal的派生類,然而Fish與Sheep的breathe不一樣,一個是在水中通過水來呼吸,一個是直接呼吸,所以基類不能確定該如何定義breathe,所以在基類中只定義了一個virtual breathe,它是一個空的虛函數,具體的函數在子類中分別定義,程序一般運行時,找到類,如果它有基類,再找到它的基類,最后運行的是基類中的函數,這時,它在基類中找到的是virtual標識的函數,它就會再回到子類中找同名函數,派生類也叫子類,基類也叫父類,這就是虛函數的產生,和類的多態(tài)性的體現。

  這里的多態(tài)性是指類的多態(tài)性。

  函數的多態(tài)性是指一個函數被定義成多個不同參數的函數。當你調用這個函數時,就會調用不同的同名函數。

一般情況下(不涉及虛函數),當我們用一個指針/引用調用一個函數的時候,被調用的函數是取決于這個指針/引用的類型。

當設計到多態(tài)性的時候,采用了虛函數和動態(tài)綁定,此時的調用就不會在編譯時候確定而是在運行時確定。不在單獨考慮指針/引用的類型而是看指針/引用的對象的類型來判斷函數的調用,根據對象中虛指針指向的虛表中的函數的地址來確定調用哪個函數

現在我們看一個體現c++多態(tài)性的例子,看看輸出結果:

#include <iostream> #include <stdlib.h>using namespace std; 

class CA 
{ 
public: 
    void f() 
    { 
        cout << "CA f()" << endl; 
    } 
    virtual void ff() 
    { 
        cout << "CA ff()" << endl; 
        f(); 
    } 
}; 

class CB : public CA 
{ 
public : 
    virtual void f() 
    { 
        cout << "CB f()" << endl; 
    } 
    void ff() 
    { 
        cout << "CB ff()" << endl; 
        f(); 
        CA::ff(); 
    } 
}; 
class CC : public CB 
{ 
public: 
    virtual void f() 
    { 
        cout << "C f()" << endl; 
    } 
}; 

int main() 
{ 
    CB b; 
    CA *ap = &b; 
    CC c; 
    CB &br = c; 
    CB *bp = &c; 

    ap->f(); 
    cout << endl;

    b.f(); 
    cout << endl;

    br.f(); 
    cout << endl;

    bp->f(); 
    cout << endl;

    ap->ff(); 
    cout << endl;

    bp->ff(); 
    cout << endl;    return 0; 
}

輸出結果:

CA f()CB f()C f()C f()CB ff()CB f()CA ff()CA f()CB ff()C f()CA ff()CA f()

感謝各位的閱讀!看完上述內容,你們對C++如何實現多態(tài)機制大概了解了嗎?希望文章內容對大家有所幫助。如果想了解更多相關文章內容,歡迎關注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI