最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

c++ 虛函數(shù),虛表相關(guān)總結(jié)

 更新時間:2021年03月01日 10:35:59   作者:程序員楊小哥  
這篇文章主要介紹了c++ 虛函數(shù),虛表的的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用c++,感興趣的朋友可以了解下

面向?qū)ο?,從單一的類開始說起。

class A
{
private:
    int m_a;
    int m_b;
}; 

這個類中有兩個成員變量,都是int類型,所以這個類在內(nèi)存中占用多大的內(nèi)存空間呢?

sizeof(A), 8個字節(jié),一個int占用四個字節(jié)。下圖驗證:

這兩個數(shù)據(jù)在內(nèi)存中是怎樣排列的呢?

原來是這樣,我們根據(jù)debug出來的地址畫出a對象在內(nèi)存的結(jié)構(gòu)圖

如果 class A 中包含成員函數(shù)呢? A 的大小又是多少?

class A
{
public:
    void func1() {}    
private:
    int m_a;
    int m_b;
}; 

直接告訴你答案,類的成員函數(shù)多大? 沒人能回答你,并且不是本文的重點,類的成員函數(shù)是放在代碼區(qū)的,不算在類的大小內(nèi)。

類的對象共享這一段代碼,試想,如果每一個對象都有一段代碼,光是存儲這些代碼得占用多少空間?所以同一個類的對象共用一段代碼。

共用同一段代碼怎么區(qū)分不同的對象呢?

實際上,你在調(diào)用成員函數(shù)時,a.func1() 會被編譯器翻譯為 A::func1(&a),也就是A* const this, this 就是 a 對象的地址。

所以根據(jù)this指針就能找到對應(yīng)的數(shù)據(jù),通過這同一段代碼來處理不同的數(shù)據(jù)。

接下來我們討論一下繼承,子類繼承父類,將會繼承父類的數(shù)據(jù),以及父類函數(shù)的調(diào)用權(quán)。

以下的測試可以驗證這個情況。

class A
{
public:
    void func1() { cout << "A func1" << endl; }
private:
    int m_a;
    int m_b;
};

class B : public A
{
public:
    void func2() { cout << "B func2" << endl; }
private:
    int m_c;
};

int main(int argc, char const* argv[])
{
    B b;
    b.func1();
    b.func2();
    return 0;
} 

輸出:

// A func1
// B func2 

那么對象b在內(nèi)存中的結(jié)構(gòu)是什么樣的呢?

繼承關(guān)系,先把a中的數(shù)據(jù)繼承過來,再有一份自己的數(shù)據(jù)。

每個包含虛函數(shù)的類都有一個虛表,虛表是屬于類的,而不是屬于某個具體的對象,一個類只需要一個虛表即可。同一個類的所有對象都使用同一個虛表。

為了指定對象的虛表,對象內(nèi)部包含指向一個虛表的指針,來指向自己所使用的虛表。為了讓每個包含虛表的類的對象都擁有一個虛表指針,編譯器在類中添加了一個指針,*__vptr,用來指向虛表。這樣,當(dāng)類的對象在創(chuàng)建時便擁有了這個指針,且這個指針的值會自動被設(shè)置為指向類的虛表。

class A
{
public:
    void func1() { cout << "A func1" << endl; }
    virtual void vfunc1() { cout << "A vfunc1" << endl; }
private:
    int m_a;
    int m_b;
}; 

cout << sizeof(A);, 輸出12,A中包括兩個int型的成員變量,一個虛指針,指針占4個字節(jié)。

a的內(nèi)存結(jié)構(gòu)如下:

虛表是一個函數(shù)指針數(shù)組,數(shù)組里存放的都是函數(shù)指針,指向虛函數(shù)所在的位置。

對象調(diào)用虛函數(shù)時,會根據(jù)虛指針找到虛表的位置,再根據(jù)虛函數(shù)聲明的順序找到虛函數(shù)在數(shù)組的哪個位置,找到虛函數(shù)的地址,從而調(diào)用虛函數(shù)。

調(diào)用普通函數(shù)則不像這樣,普通函數(shù)在編譯階段就指定好了函數(shù)位置,直接調(diào)用即可。

class A
{
public:
    void func1() { cout << "A func1" << endl; }
    virtual void vfunc1() { cout << "A vfunc1" << endl; }
private:
    int m_a;
    int m_b;
};

class B : public A
{
public:
    void func1() { cout << "B func1" << endl; }
    virtual void vfunc2() { cout << "B vfunc2" << endl; }
private:
    int m_a;
}; 

像這樣,B類繼承自A類,B中又定義了一個虛函數(shù)vfunc2, 它的虛表又是怎么樣的呢?

給出結(jié)論,虛表如下圖所示:

我們來驗證一下:

A a;
B b;
void(*avfunc1)() = (void(*)()) *(int*) (*(int*)&a);
void (*bvfunc1)() = (void(*)()) *(int*) *((int*)&b);
void (*bvfunc2)() = (void(*)()) * (int*)(*((int*)&b) + 4);
avfunc1();
bvfunc1();
bvfunc2();

來解釋一下代碼: void(*avfunc1)() 聲明一個返回值為void, 無參數(shù)的函數(shù)指針 avfunc1, 變量名代表我們想要取A類的vfunc1這個虛函數(shù)。

右半部分的第一部分,(void(*)()) 代表我們最后要轉(zhuǎn)換成對應(yīng)上述類型的指針,右邊需要給一個地址。

我們看 (*int(*)&a), 把a的地址強轉(zhuǎn)成int*, 再解引用得到 虛指針的地址。

*(int*) (*(int*)&a) 再強轉(zhuǎn)解引用得到虛表的地址,最后強轉(zhuǎn)成函數(shù)指針。

同理得到 bvfunc1, bvfunc2, +4是因為一個指針占4個字節(jié),+4得到虛表的第二項。

覆蓋

class A
{
public:
    void func1() { cout << "A func1" << endl; }
    virtual void vfunc1() { cout << "A vfunc1" << endl; }
private:
    int m_a;
    int m_b;
};

class B : public A
{
public:
    void func1() { cout << "B func1" << endl; }
    virtual void vfunc1() { cout << "B vfunc1" << endl; }
private:
    int m_a;
}; 

子類重寫父類的虛函數(shù),需要函數(shù)簽名保持一致,該種情況在內(nèi)存中的結(jié)構(gòu)為:

多態(tài)

父類指針指向子類對象的情況下,如果指針調(diào)用的是虛函數(shù),則編譯器會將會從虛指針?biāo)傅奶摵瘮?shù)表中找到對應(yīng)的地址執(zhí)行相應(yīng)的函數(shù)。

子類很多的話,每個子類都覆蓋了對應(yīng)的虛函數(shù),則通過虛表找到的虛函數(shù)執(zhí)行后不就執(zhí)行了不同的代碼嘛,表現(xiàn)出多態(tài)了嘛。

我們把經(jīng)過虛表調(diào)用虛函數(shù)的過程稱為動態(tài)綁定,其表現(xiàn)出來的現(xiàn)象稱為運行時多態(tài)。動態(tài)綁定區(qū)別于傳統(tǒng)的函數(shù)調(diào)用,傳統(tǒng)的函數(shù)調(diào)用我們稱之為靜態(tài)綁定,即函數(shù)的調(diào)用在編譯階段就可以確定下來了。

那么,什么時候會執(zhí)行函數(shù)的動態(tài)綁定?這需要符合以下三個條件。

  • 通過指針來調(diào)用函數(shù)
  • 指針 upcast 向上轉(zhuǎn)型(繼承類向基類的轉(zhuǎn)換稱為 upcast)
  • 調(diào)用的是虛函數(shù)

為什么父類指針可以指向子類?

子類繼承自父類,子類也屬于A的類型。

最后通過一個例子來體會一下吧:

class Shape
{
public:
    virtual void draw() = 0;
};

class Rectangle : public Shape
{
    void draw() { cout << "rectangle" << endl; }
};

class Circle : public Shape
{
    void draw() { cout << "circle" << endl; }
};

class Triangle : public Shape
{
    void draw() { cout << "triangle" << endl; }
};


int main(int argc, char const *argv[])
{
    vector<Shape*> v;
    v.push_back(new Rectangle());
    v.push_back(new Circle());
    v.push_back(new Triangle());
    for (Shape* p : v) {
        p->draw();
    }
    return 0;
} 

有些話是大白話,哈哈,如果這篇文章寫的不錯,解決了你的疑惑的話,點個贊再走吧!

不對的地方也請指出來,大家一起學(xué)習(xí)進(jìn)步。

以上就是c++ 虛函數(shù),虛表相關(guān)總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于c++ 虛函數(shù),虛表的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • C++浮點數(shù)類型詳情

    C++浮點數(shù)類型詳情

    這篇文章主要介紹了C++浮點數(shù)類型,浮點數(shù)是C++的第二組基本類型,它能夠表示帶小數(shù)部分的數(shù)字。不僅如此,浮點數(shù)的范圍也比int更大,可以表示更大范圍的數(shù)字。下面來我們大家一起來學(xué)習(xí)學(xué)習(xí)內(nèi)容
    2021-11-11
  • Qt利用QScroller實現(xiàn)home界面滑動效果

    Qt利用QScroller實現(xiàn)home界面滑動效果

    這篇文章主要為大家詳細(xì)介紹了Qt如何利用QScroller實現(xiàn)home界面滑動效果,文中的實現(xiàn)過程講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2022-11-11
  • C語言設(shè)置和取得socket狀態(tài)的相關(guān)函數(shù)用法

    C語言設(shè)置和取得socket狀態(tài)的相關(guān)函數(shù)用法

    這篇文章主要介紹了C語言設(shè)置和取得socket狀態(tài)的相關(guān)函數(shù)用法,分別是setsockopt()函數(shù)和getsockopt()函數(shù)的使用介紹,需要的朋友可以參考下
    2015-09-09
  • C++排序算法之選擇排序解析

    C++排序算法之選擇排序解析

    這篇文章主要介紹了C++排序算法之選擇排序解析,遍歷數(shù)組選擇找到最大值,記錄最大值下標(biāo)maxindax,然后將最大值與最后一個值交換, 在剩下的待排序數(shù)組中,重新找到最大值,重復(fù)第一步,循環(huán)操作,直至數(shù)組排序完成,需要的朋友可以參考下
    2023-10-10
  • C語言中遞歸和排列組合詳解

    C語言中遞歸和排列組合詳解

    大家好,本篇文章主要講的是C語言中遞歸和排列組合詳解,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2022-01-01
  • C++超詳細(xì)講解auto與nullptr的使用

    C++超詳細(xì)講解auto與nullptr的使用

    C++11提供了nullptr用來取代0或者NULL。在C++11之前,使用NULL為空指針賦初值,但NULL其實就是0,這時會把NULL當(dāng)成0來用;在C++11中,我們在聲明一個變量或?qū)ο?,指定它的類型時,可以不使用變量本身的類型而使用auto替代
    2022-05-05
  • C++異常使用詳解(看這一篇就夠了)

    C++異常使用詳解(看這一篇就夠了)

    C++中的異常是指在程序執(zhí)行過程中發(fā)生錯誤,導(dǎo)致程序無法正常運行的情況,下面這篇文章主要給大家介紹了關(guān)于C++異常使用的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-10-10
  • c++ class中成員與分配內(nèi)存的問題詳解

    c++ class中成員與分配內(nèi)存的問題詳解

    很多人都知道C++類是由結(jié)構(gòu)體發(fā)展得來的,所以他們的成員變量(C語言的結(jié)構(gòu)體只有成員變量)的內(nèi)存分配機制是一樣的,下面這篇文章主要給大家介紹了關(guān)于c++ class中成員與分配內(nèi)存問題的相關(guān)資料,需要的朋友可以參考下
    2021-10-10
  • C++運算符重載圖文詳解

    C++運算符重載圖文詳解

    運算符重載的方法是定義一個重載運算符的函數(shù),在需要執(zhí)行被重載的運算符時,系統(tǒng)就自動調(diào)用該函數(shù),以實現(xiàn)相應(yīng)的運算。也就是說,運算符重載是通過定義函數(shù)實現(xiàn)的
    2021-09-09
  • Linux?C/C++?timeout命令實現(xiàn)運行具有時間限制功能

    Linux?C/C++?timeout命令實現(xiàn)運行具有時間限制功能

    inux?timeout命令的一個屬性是時間限制??梢詾槿魏蚊钤O(shè)置時間限制。如果時間到期,命令將停止執(zhí)行,這篇文章主要介紹了Linux?C/C++?timeout命令實現(xiàn)(運行具有時間限制),需要的朋友可以參考下
    2023-02-02

最新評論

游戏| 德安县| 汝南县| 张家界市| 清水河县| 满洲里市| 延津县| 义马市| 满洲里市| 石家庄市| 信宜市| 闵行区| 新宁县| 葵青区| 濮阳市| 许昌县| 观塘区| 奎屯市| 莆田市| 兰溪市| 松桃| 平泉县| 米泉市| 疏附县| 浮梁县| 山阳县| 屏南县| 顺义区| 五河县| 田东县| 无为县| 古交市| 徐闻县| 宁海县| 北宁市| 仲巴县| 德令哈市| 武安市| 神农架林区| 旌德县| 平昌县|