1. 程式人生 > >虛擬函式 C++

虛擬函式 C++

C++ 虛擬函式

虛擬函式

基類中使用virtual關鍵字宣告的函式,稱為虛擬函式。
虛擬函式的實現,通過虛擬函式表來實現的。即V-table 這個表中有一個類,用於儲存虛擬函式的地址。解決其繼承,覆蓋的問題,用於保證其真實反映的函式。這樣有虛擬函式的例項,將會儲存在這個例項的記憶體中。即用父類的指標,操作子類的時候,通過虛擬函式表來實現找尋到父類。

定義下方的一個類

class Base{
public:
    virtual void f(){
        cout << "Base::f" << endl;
    }
    virtual void g(){
        cout << "Base::g" << endl;
    }
    virtual void h(){
        cout << "Base::h" << endl;
    }
}

然後通過例項化得到虛擬函式表

    Base* b = new Base();

    //Fun pFun = NULL;

    cout << "虛擬函式表地址 " << (int*)(&(*b)) << endl; // 強制轉換成為指標型別,然後輸出
    cout << "虛擬函式表 - 第一個函式地址" << (int*)*(int*)(&(*b)) << endl;   // 取虛擬函式的地址,然後獲得虛擬函式的頭指向的第一個儲存函式的記憶體空間。然後獲取第一個函式的記憶體地址

然後檢視輸出的結果

虛擬函式表地址 0xb31268
虛擬函式表 - 第一個函式地址0x4c1490

根據地址可以看到,指標b指向建立的例項的地址,其首地址儲存著虛擬函式表的地址,然後再次通過指標訪問,得到虛擬函式表的第一個函式的指標的地址。

一般繼承,沒有虛擬函式覆蓋


在上方的繼承中,子類沒有過載任何父類的函式,那麼在虛擬函式列表中,就代表著

虛擬函式按照宣告的順序放入表中。
父類的虛擬函式,在子類的虛擬函式的前面。

一般繼承,有虛擬函式覆蓋

此時記憶體中地址如下

注意,最重要的一點是,地址上,覆蓋的f()函式,被放置到了父類的f()函式上。於是就可以有下方的程式


    Base *b = new Derive();
    b->f();

使用一個型別為Base的指標b指向一個新建的例項Derive(),此時對於指標b指向的虛擬函式表中,此時f()的地址被Devieive函式的地址被覆蓋, 形成如上的虛擬函式表。

此時訪問成功

多重繼承 無虛擬函式的覆蓋

假設有如上的內容

此時虛擬函式表

每個父類都有自己的虛表,子類的成員函式被放到第一個父類的表中。即第一個父類的表是按照宣告的順序來判斷。

多重繼承 有虛擬函式覆蓋

此時表如下

此時父類的被替換了。

這樣就實現了多重繼承,程式碼如下

    Derive d;
    Base1 *b1 = &d;
    Base2 *b2 = &d;
    Base3 *b3 = &d;
    b1->f();
    b2->f();
    b3->f();

此時程式碼如上。

純虛擬函式

virtual int area() = 0;

實現一個純虛擬函式,此時可以在派生類中更好的定義純虛擬函式。