C++中的虛函數以及虛函數表


一.虛函數的定義

被virtual關鍵字修飾的成員函數,目的是為了實現多態

ps:

關於多態【接口和實現分離,父類指針指向子類的實例,然后通過父類指針調用子類的成員函數,這樣可以讓父類指針擁有多種形態,所以稱之為多態】

二.虛函數表

該表為一個類的虛函數的地址表,用於解決繼承和覆蓋的問題

1.擁有虛函數的類才有虛函數表

2.虛函數表屬於類,然后類的所有對象通過虛函數表指針共享類的虛函數表

3.虛函數表的作用:當使用父類指針來操作子類對象時,虛函數表就像一個地圖一樣,指明了實際所應該調用的函數

4.c++編譯器保證虛函數表的指針存在於對象實例中最前面的位置(為了保證在多層繼承或者多重繼承的情況下獲得函數表的性能),這意味着我們可以通過對象實例的地址得到虛函數表,然后就可以遍歷其中的虛函數指針,並且調用響應的虛函數

ps:多重繼承:多個父類,多層繼承:父類還存在父類

01

【通過虛函數表,遍歷虛函數指針,調用響應的虛函數】

#include<bits/stdc++.h>
using namespace std;
class Base
{
public:
    virtual void f()
    {
        cout << "Base::f" << endl;
    }
    virtual void g()
    {
        cout << "Base::g" << endl;
    }
    virtual void h()
    {
        cout << "Base::h" << endl;
    }

};
typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;

int main()
{
    cout << "虛函數表地址:" << (int*)(&b) << endl;
    cout << "虛函數表 — 第一個函數地址:" << (int*)*(int*)(&b) << endl;

    //通過虛函數表調用虛函數
    pFun = (Fun)*((int*)*(int*)(&b));   // Base::f()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&b)+1);  // Base::g()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&b)+2);  // Base::h()
    pFun();
}
View Code

結果:

虛函數表地址:0x477008
虛函數表 — 第一個函數地址:0x473668
Base::f
Base::g
Base::h
View Code

以上為無繼承情況

1.單層繼承無虛函數覆蓋的情況

02

03

1)虛函數按照聲明順序放入表中

2)父類虛函數在前,子類虛函數在后

3)末尾點號為虛函數表的結尾標識符,在不同編譯器下值不同

#include<bits/stdc++.h>
using namespace std;
class Base
{
public:
    virtual void f()
    {
        cout << "Base::f" << endl;
    }
    virtual void g()
    {
        cout << "Base::g" << endl;
    }
    virtual void h()
    {
        cout << "Base::h" << endl;
    }

};
class Base_son:public Base
{
public:
    virtual void f1()
    {
        cout << "Base_son::f1" << endl;
    }
    virtual void g1()
    {
        cout << "Base_son::g1" << endl;
    }
    virtual void h1()
    {
        cout << "Base_son::h1" << endl;
    }

};


typedef void(*Fun)(void);
Base_son d;

Fun pFun = NULL;

int main()
{
    cout << "虛函數表地址:" << (int*)(&d) << endl;
    cout << "虛函數表 — 第一個函數地址:" << (int*)*(int*)(&d) << endl;

    //通過虛函數表調用虛函數
    pFun = (Fun)*((int*)*(int*)(&d));   // Base::f()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+1);  // Base::g()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+2);  // Base::h()
    pFun();

    pFun =(Fun)*((int*)*(int*)(&d)+3);  // Base_son::f1()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+4);  // Base_son::g1()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+5);  // Base_son::h1()
    pFun();

    return 0;
}
View Code

結果:

虛函數表地址:0x477008
虛函數表 — 第一個函數地址:0x473668
Base::f
Base::g
Base::h
Base_son::f1
Base_son::g1
Base_son::h1
View Code

2.單層繼承有虛函數覆蓋的情況

04

05

1)覆蓋的f()函數被放到了虛函數表中原父類虛函數的位置

2)沒有被覆蓋的函數沒有變化

#include<bits/stdc++.h>
using namespace std;
class Base
{
public:
    virtual void f()
    {
        cout << "Base::f" << endl;
    }
    virtual void g()
    {
        cout << "Base::g" << endl;
    }
    virtual void h()
    {
        cout << "Base::h" << endl;
    }

};
class Base_son:public Base
{
public:
    virtual void f()
    {
        cout << "Base_son::f" << endl;
    }
    virtual void g1()
    {
        cout << "Base_son::g1" << endl;
    }
    virtual void h1()
    {
        cout << "Base_son::h1" << endl;
    }

};


typedef void(*Fun)(void);
Base_son d;

Fun pFun = NULL;

int main()
{
    cout << "虛函數表地址:" << (int*)(&d) << endl;
    cout << "虛函數表 — 第一個函數地址:" << (int*)*(int*)(&d) << endl;

    //通過虛函數表調用虛函數
    pFun = (Fun)*((int*)*(int*)(&d));   // Base_son::f()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+1);  // Base::g()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+2);  // Base::h()
    pFun();

    pFun =(Fun)*((int*)*(int*)(&d)+3);  // Base_son::g1()
    pFun();
    pFun =(Fun)*((int*)*(int*)(&d)+4);  // Base_son::h1()
    pFun();

    return 0;
}
View Code

結果:

虛函數表地址:0x477008
虛函數表 — 第一個函數地址:0x473650
Base_son::f
Base::g
Base::h
Base_son::g1
Base_son::h1
View Code

通過父類指針指向子類實例,子類覆蓋父類方法,然后調用子類的方法,這樣就實現了多態

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

3.多重繼承無虛函數覆蓋

06

07

1)每個父類都有自己的虛函數表

2)子類的虛函數被放到第一個父類的虛函數表中

這樣做是為了解決不同的父類類型指針指向同一個子類實例,而能夠調用到實際的函數

4.多重繼承存在虛函數覆蓋

08

09

1)父類虛函數表中被覆蓋的虛函數全部被替換成了子類的覆蓋虛函數

這樣我們就通過父類指向子類從而訪問子類的f()了

Derive d;
Base1 *b1 = &d;
Base2 *b2 = &d;
Base3 *b3 = &d;
b1->f(); //Derive::f()
b2->f(); //Derive::f()
b3->f(); //Derive::f()
 
b1->g(); //Base1::g()
b2->g(); //Base2::g()
b3->g(); //Base3::g()
View Code

使用虛函數表可以做一些違反c++語義的事情:

1)通過父類指針訪問子類自己的虛函數

子類的虛函數X在父類中沒有,所以子類的虛函數X沒有覆蓋父類的虛函數,但是如果我們通過父類的指針來訪問子類自己的虛函數的編譯器會報錯

Base1 *b1 = new Derive();
b1->f1();  //編譯出錯

但是我們通過虛函數表可以做到這種違背C++語義的事情:使用父類指針訪問子類自己的虛函數

2)訪問父類non-public的虛函數

如果父類的虛函數是private或protected的,但是這些feipublic的父類虛函數同樣會存在於虛函數表中,所以我們可以通過訪問虛函數表訪問到這些虛函數

附上多重繼承有虛函數覆蓋的樣例代碼:

#include <iostream>
using namespace std;
 
class Base1 {
public:
            virtual void f() { cout << "Base1::f" << endl; }
            virtual void g() { cout << "Base1::g" << endl; }
            virtual void h() { cout << "Base1::h" << endl; }
 
};
 
class Base2 {
public:
            virtual void f() { cout << "Base2::f" << endl; }
            virtual void g() { cout << "Base2::g" << endl; }
            virtual void h() { cout << "Base2::h" << endl; }
};
 
class Base3 {
public:
            virtual void f() { cout << "Base3::f" << endl; }
            virtual void g() { cout << "Base3::g" << endl; }
            virtual void h() { cout << "Base3::h" << endl; }
};
 
class Derive : public Base1, public Base2, public Base3 {
public:
            virtual void f() { cout << "Derive::f" << endl; }
            virtual void g1() { cout << "Derive::g1" << endl; }
};
 
typedef void(*Fun)(void);
 
int main()
{
            Fun pFun = NULL;
 
            Derive d;
            int** pVtab = (int**)&d;
 
            //Base1's vtable
            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);
            pFun = (Fun)pVtab[0][0];
            pFun();
 
            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);
            pFun = (Fun)pVtab[0][1];
            pFun();
 
            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);
            pFun = (Fun)pVtab[0][2];
            pFun();
 
            //Derive's vtable
            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);
            pFun = (Fun)pVtab[0][3];
            pFun();
 
            //The tail of the vtable
            pFun = (Fun)pVtab[0][4];
            cout<<pFun<<endl;
 
            //Base2's vtable
            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
            pFun = (Fun)pVtab[1][0];
            pFun();
 
            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
            pFun = (Fun)pVtab[1][1];
            pFun();
 
            pFun = (Fun)pVtab[1][2];
            pFun();
 
            //The tail of the vtable
            pFun = (Fun)pVtab[1][3];
            cout<<pFun<<endl;
 
            //Base3's vtable
            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);
            pFun = (Fun)pVtab[2][0];
            pFun();
 
            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);
            pFun = (Fun)pVtab[2][1];
            pFun();
 
            pFun = (Fun)pVtab[2][2];
            pFun();
 
            //The tail of the vtable
            pFun = (Fun)pVtab[2][3];
            cout<<pFun<<endl;
 
            return 0;
}
View Code

 

關於虛函數和普通函數:

1.類中的虛函數是動態生成的,由虛函數表的指向進行訪問,不為類的對象分配內存,沒有虛函數表,就無法訪問虛函數

2.類中的普通函數靜態生成,不為類的對象分配內存也可訪問

參考:左耳朵耗子:C++虛函數表解析

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM