日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

c++虚函数和虚函数表

發布時間:2025/3/8 c/c++ 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 c++虚函数和虚函数表 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

(1)虛基表與虛函數表是兩個完全不同的概念

  • 虛基表用來解決繼承的二義性(虛基類可以解決)。
  • 虛函數用來實現泛型編程,運行時多態。

(2)虛函數是在基類普通函數前加virtual關鍵字,是實現多態的基礎
(3)虛函數表其實不用我們管這個編譯器會幫我們做好
注:無特別說明本文的虛表均指虛函數表

(一) 什么是虛函數表?

虛函數(Virtual Function)是通過一張虛函數表(VirtualTable)來實現的。簡稱為V-Table。虛表(virtual table),編譯器為每個擁有虛函數的類都建有一張虛函數表,主是要一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其容真實反應實際的函數。這樣,在有虛函數的類的實例中這個表被分配在了這個實例的內存中,所以,當我們用父類的指針來操作一個子類的時候,這張虛函數表就顯得由為重要了,它就像一個地圖一樣,指明了實際所應該調用的函數。

C++的編譯器應該是保證虛函數表的指針存在于對象實例中最前面的位置(這是為了保證取到虛函數表的有最高的性能——如果有多層繼承或是多重繼承的情況下)

(二)含虛函數的單繼承

單繼承時,派生類中僅有一個虛函數表。這個虛函數表和基類的虛函數表不是一個表(無論派生類有沒有重寫基類的虛函數),但是如果派生類沒有重寫基類的虛函數的話,基類和派生類的虛函數表指向的函數地址都是相同的。

#include <iostream> using namespace std;class A { public :A(int a){this->a = a;}virtual void show(){cout << "a=" << a << endl;} protected:int a; };class B:public A { public:B(int a, int b) :A(a){this->b = b;} /* void show(){cout << "a= " << a << " b = " << b << endl;}*/ protected:int b; };int main() {A a(2);a.show();B b(1, 3);b.show();return 0; }

此時類B,沒有重寫類A的show方法,僅僅是繼承了父類

可以看出,兩個類的__vfptr的值不同,但是每個槽內部的函數地址都是相同的。
下面在類B中重寫類A的show方法:

#include <iostream> using namespace std;class A { public :A(int a) {this->a = a;}virtual void show() {cout << "a=" << a << endl;} protected:int a; };class B:public A { public:B(int a, int b) :A(a) {this->b = b;}void show() {cout << "a= " << a << " b = " << b << endl;} protected:int b; };int main() {A a(2);a.show();B b(1, 3);b.show();return 0; }


通過上面可以總結:派生類內存布局,先是復制一份基類內存布局,然后是自己的布局(注意內存對齊)。虛表指針指向自己的虛表,派生類虛函數地址如果自己未覆蓋,那么就是基類的,否則是自己的函數地址,并且可以看到派生類一旦重寫父類的虛函數就會覆蓋原來繼承的,
關于這一點的講解,我認為這個大佬講的不錯:虛函數表解析

(三)含虛函數的多繼承

多繼承情況下,派生類中有多個虛函數表,虛函數的排列方式和繼承的順序一致。派生類重寫函數將會覆蓋所有虛函數表的同名內容,派生類自定義新的虛函數將會在第一個類的虛函數表的后面進行擴充。

#include<iostream> using namespace std; class Base { public:Base(int base){ this->base = base;}virtual void show() {cout << base << endl;}virtual void print() { cout << "test Base " << endl; } protected:int base; }; class BaseA { public:BaseA(int basea){this->basea = basea;}virtual void show(){cout << basea << endl;}virtual void watch() { cout << "test BaseA" << endl; } protected:int basea; }; class BaseB :public Base, public BaseA { public:BaseB(int base, int basea, int baseb) :Base(base), BaseA(basea){this->baseb = baseb;}void show(){cout << base << basea << baseb << endl;}virtual void print() { cout << "test B " << endl; }//重寫base的print方法,沒有重寫BASEA的watch方法 private:int baseb; }; int main() {Base base(1);BaseA baseA(2);BaseB baseB(3,3, 3);return 0; }


這里通過編譯器的部分可以看出來,未被重寫的虛函數指針將和基類指向同一個位置,一旦被重寫,函數指針就指向新的位置。先按照繼承順序,從左到右排布基類的布局包括虛標指針,然后排布自己的指針和數據;派生類虛表排布形式是按照繼承順序,是繼承來的虛函數,如果有覆蓋則換成自己的函數地址;然后是下一個基類,直至基類排布完畢。繼承來的多張表是獨立的(從內存布局中的多個虛表指針可以看出),且使用首地址+偏移量的形式來訪問。
注意:如果派生類有自己的虛函數則會加在第一個基類的虛表末尾
以前關于虛函數表不太明白,今天網上搜集了些資料,在這里總結一下,如果有錯誤歡迎討論啊~

附錄

這里介紹一種查看內存布局的方法:
1.點擊圖中紅圈打開“開發人員命令提示符

2.通過dos命令進入代碼所在目錄
3.使用cl命令的"/d1 reportAllClassLayout或reportSingleClassLayoutXXX"選項。這里的reportAllClassLayout選項會打印大量相關類的信息,一般用處不大。而reportSingleClassLayoutXXX選項的XXX代表要編譯的代碼中類的名字(這里XXX類),打印XXX類的內存布局和虛函數表(如果代碼中沒有對應的類,則選項無效)。
例如我的:

在vs中查看變量的另一種方法是:
在調試模式下,點擊窗口==>自動窗口就可以了

注意一定是調試模式,否則沒有此按鈕,同時注意設置斷點。

參考文章:
虛函數表解析
C++虛函數和虛函數表原理

總結

以上是生活随笔為你收集整理的c++虚函数和虚函数表的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。