C++虛函數和多態

跟狄泰軟件學院的唐老師學習C++已經有一段時間了,發現虛函數的概念一直不是很清楚,今天把唐老師的課程重新看了一下,先將相關知識點總結一下。

一、多態的概念

  多態值通過類的指針(引用)調用類的方法時,根據實際的對象決定調用函數的具體目標。也就是說同樣的調用語句在實際運行時有多種不同的表現形態。

二、多態的實現方式

  C++ 直接支持多態的概念,通過使用virtual關鍵字對多態進行支持。被virtual聲明的函數被子類重寫後具有多態特性。被virtual聲明的函數叫做虛函數。

三、示例一

#include <iostream>#include <string>using namespace std;class Parent
{public:    virtual void print()
    {        cout << "I'm Parent." << endl;
    }
};class Child : public Parent
{public:    void print()
    {        cout << "I'm Child." << endl;
    }
};void how_to_print(Parent* p)
{
    p->print();     // 展現多態的行爲}int main()
{
    Parent p;
    Child c;

    how_to_print(&p);    // Expected to print: I'm Parent.
    how_to_print(&c);    // Expected to print: I'm Child.

    return 0;
}12345678910111213141516171819202122232425262728293031323334353637381234567891011121314151617181920212223242526272829303132333435363738

程序運行結果如下:

這裏寫圖片描述

  上面代碼中通過調用函數void how_to_print(Parent* p)來體現出多態性,雖然函數的參數都爲Parent* p,但調用後的結果確不相同。下圖爲void how_to_print(Parent* p)函數執行流程:

這裏寫圖片描述

四、C++多態的實現原理

  1.當類中聲明虛函數時,編譯器會在類中生成一個虛函數表; 
   
  2.虛函數表是一個存儲成員函數地址的數據結構; 
   
  3.虛函數表是由編譯器自動生成與維護的; 
   
  4.virtual成員函數地址會被編譯器放入虛函數表; 
   
  5.存在虛函數表時,每個對象中都有一個指向虛函數表的指針,且放在類的最前面。

下圖爲虛函數調用流程:

這裏寫圖片描述

下面通過一個示例來說明:

#include <iostream>#include <string>using namespace std;class Parent
{private:    int m_data;public:
    Parent() : m_data(10)
    {

    }    virtual void print()
    {        cout << "I'm Parent." << endl;
    }
};class Child : public Parent
{public:    void print()
    {        cout << "I'm Child." << endl;
    }
};struct Data {   // 模擬Child在內存中的分佈
    int *p;    int data;
};typedef void (*Fun)();int main()
{
    Parent* c = new Child();

    Data* data = (Data*)c;    cout << "sizeof(Child): " << sizeof(Child) << endl;    cout << endl;    cout << "data->p: "<< data->p << endl;    cout << "*(data->p): " << *(data->p) << endl;    cout << "data->data:" << data->data << endl;    cout << endl;

    Fun fun = (Fun)(*(data->p));
    fun();    return 0;
}12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758591234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859

程序運行結果如下圖所示:

這裏寫圖片描述

  1.sizeof(Child): 8:表明Child除了一個繼承自父類Parent的int m_data;成員變量所佔用4字節之外,還多出了4字節。這4字節便用來存放虛函數表; 
   
  2.data->p: 0x404318:虛函數表地址; 
   
  3.data->data:10:打印出的是m_data對應的值,更進一步證明虛函數表是存儲在類的最前面,佔用4字節; 
   
  4.*(data->p): 4204792:虛函數表中存儲的內容,是對應虛函數的入口地址,即void print()的入口地址。通過下一句的打印I'm Child.即可證明。

五、構造函數和析構函數能否聲明爲虛函數

  1.構造函數不可能成爲虛函數,因爲在構造函數執行結束後,虛函數表指針纔會被正確的初始化,所以在構造函數中不可能發生多態。 
   
  2.析構函數可以設計成虛函數。若發生繼承關係時,父類的析構函數一定要實現爲虛函數,否則若通過父類指針釋放子類對象時無法調用子類的析構函數,會造成資源釋放不完全。 
   
  3.析構函數中也不可能發生多態行爲。因爲在析構函數執行時,虛函數表指針已經被銷燬,只能調用當前類中定義的版本。

  • 1


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