C++中多態特性深入探究---虛函數

首先,多態是什麼?

  • 摘抄自 多態(維基百科)
    —— 多態(英語:polymorphism),是指計算機程序運行時,相同的消息可能會送給多個不同的類別之對象,而系統可依據對象所屬類別,引發對應類別的方法,而有不同的行爲。簡單來說,所謂多態意指相同的消息給予不同的對象會引發不同的動作稱之。

  • cplusclpus中有這麼一句話:

One of the key features of class inheritance is that a pointer to a derived class is type-compatible with a pointer to its base class. Polymorphism is the art of taking advantage of this simple but powerful and versatile feature.

即,在C++中指向子類的指針和指向基類的指針是兼容的。

  • 所以,在C++中,我們可以通過指針指向不同的對象來實現多態(引用和指針類似,雖然有些差別,但是這裏不展開討論)

    虛函數機制

  • 參考《Effective C++》條款07中對虛表指針以及虛函數表的講解,這裏闡述下虛函數機制的原理:

    —- 在C++中,若在類中定義了虛函數,則編譯器會爲改類生成一個虛函數表,這個表中保存了該類以及父類所有的虛函數,並在實例化該類時,會在類對象中添加一個虛表指針,這個指針就只向該虛表指針,當使用指針或引用調用函數時,C++會根據對象中保存的虛表指針查找需要調用的函數,具體的細節下面慢慢分析。

 class Fa
{
    int Fa_a;
public:
    void foo()
    {
        cout << "父類的foo函數 \n";
    }
};

class childA:public Fa
{
    int CA_a;
public:
     void foo()
    {
        cout << "子類A的foo函數 \n";
    }
};

VS中開啓類內存佈局查看後的圖片

  • 在沒有定義虛函數時,內存佈局中只存在父類和子類的數據成員對象
class Fa
{
    int Fa_a;
public:
    virtual void foo()
    {
        cout << "父類的foo函數 \n";
    }
};

class childA:public Fa
{
    int CA_a;
public:
     void foo()
    {
        cout << "子類A的foo函數 \n";
    }
};

添加了虛函數後的內存佈局

  • 在添加了虛函數後,內存佈局中增加了一個vfptr,並且生成了一個vftable,分別是虛表指針和虛函數表。
class Fa
{
    int Fa_a;
public:
    virtual void foo()
    {
        cout << "父類的foo函數 \n";
    }
};



class childA:public Fa
{
    int CA_a;
};

只在父類中定義foo函數

class Fa
{
    int Fa_a;
};



class childA:public Fa
{
    int CA_a;
public:
     virtual void foo()
    {
        cout << "子類A的foo函數 \n";
    }
};

只在子類定義foo函數

在這兩個例子中需要注意兩點:

  1. 當在父類中先定義虛函數時,vfptr出現在父類下面,當子類先定義虛函數時,vfptr出現在子類下面
  2. 當在子類中定義了和父類中虛函數相同的函數時,虛函數表中保存的是子類的虛函數,當子類中沒有定義與父類虛函數相同的函數時,虛函數表中保存的是父類的虛函數。

—- 這裏不難得出一下兩點結論:

  1. 子類會繼承父類的虛函數表,並且當子類定義了與父類相同的函數(返回值,函數簽名都相同)時,子類的虛函數會覆蓋父類的虛函數保存到虛函數表中。
  2. 當子類出現父類中沒有的虛函數時,子類的虛函數會被添加到虛函數表中,並且偏移量大於任意父類的虛函數。

— 上面的例子還不是很清晰的說明了第二點,下面在放一個清晰一點的例子:

class Fa
{
    int Fa_a;
public:
    virtual void foo()
    {
        cout << "父類的foo函數 \n";
    }
    virtual  void FFF()
    {
        cout << "";
    }
};


class childA:public Fa
{
    int CA_a;
public:
     virtual void foo()
    {
        cout << "子類A的foo函數 \n";
    }

     virtual void funcA()
     {
         cout << "子類A的func函數\n";
     }
};

子類新增函數

  • 這個例子可以讓我們清楚的理解上面兩點結論,父類中先定義了虛函數,於是vfptr在Fa下面,子類定義了與父類相同的函數,於是子類的虛函數覆蓋了父類的虛函數,子類新增了虛函數,於是虛函數表中添加了該虛函數,並且在父類虛函數的後面。

  • 理解了原理後我們就可以使用虛函數來實現多態了:

#include<iostream>

using namespace std;

class Fa
{
    int Fa_a;
public:
    virtual void foo()
    {
        cout << "父類的foo函數 \n";
    }
};



class childA:public Fa
{
    int CA_a;
public:
     virtual void foo()
    {
        cout << "子類A的foo函數 \n";
    }
};

class childB :public Fa
{
    int CB_a;
public:
    virtual void foo()
    {
        cout << "子類B的foo函數\n";
    }
};

void func()
{
    Fa * ff = new Fa();
    ff->foo();
    ff =new childA();
    ff->foo();
    ff = new childB();
    ff->foo();
}


int main()
{
    func();
    system("pause");
    return 0;
}

這裏寫圖片描述

  • 可以看到,我們使用了同一個ff指針,卻調用了三個不同的函數,這就是多態了。

下一篇會講解虛函數的注意事項

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