麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學院 > 開發設計 > 正文

C++對象模型

2019-11-14 09:28:54
字體:
來源:轉載
供稿:網友

 1.C++對象模型概述

有兩個概念可以解釋C++對象模型 1.語言中直接支持面向對象程序設計的部分 包括了構造函數、析構函數、多態、虛函數等等. 2.對于各種支持的底層實現機制 對象模型研究的是對象在存儲上的空間與時間上的優化,并對C++面向對象技術加以支持,如以虛指針、虛表機制支持多態機制.

2.理解虛函數表

2.1多態與虛表

C++中虛函數的作用主要是為了實現多態機制,多臺,簡單的來說,是指在繼承層次中,父類的指針可以具有多種形態——當它指向某個子類對象時,通過它能夠調用子類的函數,而非父類的函數

class Base { virtual void PRint(void);}class Drive1:public Base{virtual void print(void);}class Drive2:public Base{vittual void print(void);}Base * ptr1 = new Base;Base *ptr2 = new Driver1;Base *ptr3 = new Driver2;ptr1->print();//調用Base::print()ptr2->print();//調用Drive1::print()ptr3->print();//調用Driver2::print()

這是一種運行期多態,即父類指針唯有在程序運行時才能知道所指的真正類型是什么,這種運行期決議,是通過虛函數表來實現的.

3.2 使用指針訪問虛表

#include<iostream>using namespace std;class Base{public: Base(int i):baseI(i){}; virtual void print(void){ cout <<"調用了虛函數Base::print()"<<endl; } virtual void setI(){ cout << "調用了虛函數Base::setI();"<<endl; } virtual ~Base(){}private: int baseI;};int main(int argc,char *argv[]){ Base b(1000); int *vptrAdree = (int *)(&b); cout << "虛函數表(vptr)的地址是: "<<vptrAdree <<endl; typedef void(*Fun)(void); Fun vfunc = (Fun)*((int *)*(int *)(&b)); cout << "第一個虛函數的地址是:" <<(int *)*(int *)(&b) <<endl; cout << "通過地址,調用虛函數Base::print():"; vfunc(); return 0;}運行結果:yang@yang:~/C++/對象模型$ ./a.out 輸出一:虛函數表(vptr)的地址是: 0x7ffc7fa95d20輸出二:第一個虛函數的地址是:0x400dd0輸出三:通過地址,調用虛函數Base::print():調用了虛函數Base::print()

輸出一詳解:我們強行把類對象的地址轉換為int* 類型,取得了虛函數指針的地址.虛函數指針指向虛函數表,虛函數表中存儲的是一系列虛函數的地址,虛函數地址出現的順序與類中虛函數聲明的順序一致,對虛函數指針地址值解引用,可以得到虛函數表的地址,也即是虛函數表的第一個虛函數的地址: 輸出二詳解:

我們把虛表指針的值取出來:(int )(&b);它是一個地址,虛函數表的地址.把虛函數表的地址強制轉換成int * :(int )(int *)(&b)再把它轉換成我們Func指針類型:(Fun )(int ) * (int *)(&b);

3.對象模型概述

3.1對象模型概述

在C++中,有兩種數據成員,static和nonstatic,以及三種類成員函數:static、nonstatic 和virtual;

這里寫圖片描述 現在我們有一個類Base,它包含了上面這5種類型的數據或函數

#include<iostream>using namespace std;class Base{public: Base(int i):BaseI(i){}; int getI(){ return BaseI; } static void countI(){}; virtual void print(void){ cout << "Base::print()"; } virtual ~Base(){}private: int baseI; static int baseS;};

3.2 非繼承下的C++ 對象模型

概述:在此模型下,nonstatic數據成員被置于每一個類對象中,而static數據成員被置于類對象之外。static與nonstatic函數也都放在類對象之外.而對于虛函數,則通過虛函數表+虛函數指針來支持,具體如下:

每一個類生成一個表格,稱為虛表,虛表中存放著一堆指針,這些指針指向該類每一個虛函數,虛表中的函數地址按聲明時的順序排列,不過當子類中有多個重載函數時例外,后面會討論. 每個類對象都擁有一個虛表指針(vptr),由編譯器為其生成,虛表指針的設定與重置皆由類的復制控制(也即是構造函數、析構函數、賦值操作符)來完成,vptr的位置由編譯器來決定,許多編譯器把vptr放在一個類對象的最前端。關于數據成員布局的內容,在后面會詳細分析。另外,虛函數表的前面設置了一個指向type_info的指針,用以支持RTTI(Run Time Type Identification,運行時類型識別)。RTTI是為多態而生成的信息,包括對象繼承關系,對象本身的描述等,只有具有虛函數的對象在會生成。

在此模型下,Base對象的對象模型如下: 這里寫圖片描述

4.繼承下的C++對象模型

4.1單繼承

#include<iostream>using namespace std;class Derive:public Base{public: Derive(int d):DeriveI(d){}; //重載父類的虛函數 virtual void print(void) { cout << "Drive::Dirve_print()"; } //Derive聲明的新的虛函數 virtual void Drive_print(){ cout << "Drive::Drive_print()"; }private: int DeriveI;};

在C++對象模型中,對于一般繼承(這個是相對于虛擬繼承而言),若子類重寫了父類的虛函數,則子類虛函數將覆蓋虛表中對應父類虛函數(注意子類和父類擁有各自的一個虛函數表);若子類并無overwrite父類虛函數,而是聲明了自己的新的虛函數,則該虛函數地址將擴充到虛函數表最后,而對于虛繼承,若子類overwrite父類虛函數m同樣地將覆蓋父類子物體中虛函數表對應位置,若子類聲明了自己的新的虛函數,則編譯器為其子類增加一個新的虛表指針vptr. 這里寫圖片描述

4.2多繼承

4.2.1一般的多重繼承

單繼承中(一般繼承),子類會擴展父類的虛函數表,在多繼承中,子類含有多個父類的子對象,該往哪個父類的虛函數表擴展呢?當子類overwrite了父類的函數,需要覆蓋多個父類的虛函數表嗎?

子類的虛函數被放在聲明的第一個基類的虛函數表中.overwrite時,所有基類的print()函數都被子類的print()函數覆蓋.內存布局中,父類按照其聲明順序排列. class Base{public: Base(int i) :baseI(i){}; virtual ~Base(){} int getI(){ return baseI; } static void countI(){}; virtual void print(void){ cout << "Base::print()"; }private: int baseI; static int baseS;};class Base_2{public: Base_2(int i) :base2I(i){}; virtual ~Base_2(){} int getI(){ return base2I; } static void countI(){}; virtual void print(void){ cout << "Base_2::print()"; }private: int base2I; static int base2S;};class Drive_multyBase :public Base, public Base_2{public: Drive_multyBase(int d) :Base(1000), Base_2(2000) ,Drive_multyBaseI(d){}; virtual void print(void){ cout << "Drive_multyBase::print" ; } virtual void Drive_print(){ cout << "Drive_multyBase::Drive_print" ; }private: int Drive_multyBaseI;};

此時Drive_multyBase的對象模型是這樣的: 這里寫圖片描述

4.2.2菱形繼承(子類間接繼承多次同一個基類)

菱形繼承也稱為重復繼承,它指的是基類被某個派生類簡單重復繼承了多次,這樣,派生類對象中擁有多份基類實例:看代碼:

class B{public: int ib;public: B(int i=1) :ib(i){} virtual void f() { cout << "B::f()" << endl; } virtual void Bf() { cout << "B::Bf()" << endl; }};class B1 : public B{public: int ib1;public: B1(int i = 100 ) :ib1(i) {} virtual void f() { cout << "B1::f()" << endl; } virtual void f1() { cout << "B1::f1()" << endl; } virtual void Bf1() { cout << "B1::Bf1()" << endl; }};class B2 : public B{public: int ib2;public: B2(int i = 1000) :ib2(i) {} virtual void f() { cout << "B2::f()" << endl; } virtual void f2() { cout << "B2::f2()" << endl; } virtual void Bf2() { cout << "B2::Bf2()" << endl; }};class D : public B1, public B2{public: int id;public: D(int i= 10000) :id(i){} virtual void f() { cout << "D::f()" << endl; } virtual void f1() { cout << "D::f1()" << endl; } virtual void f2() { cout << "D::f2()" << endl; } virtual void Df() { cout << "D::Df()" << endl; }};

我們根據單繼承,我們可以分析出B1,B2類繼承B類時的內存布局,又根據一般多繼承,我們可以分析D類的內存布局: 這里寫圖片描述 D類對象內存布局,圖中綠色表示b1類子對象實例,藍色表示的是b2類子對象實例,紅色表示的是D類子對象實例,從圖中可以看到,由于D類間接繼承了B類兩次,導致D類對象中含有兩個B類的數據成員ib,一個屬于來源B1類,一個來源B2類。這樣不僅增大了空間,更重要的是引起了程序歧義:

D d;d.ib =1 ; //二義性錯誤,調用的是B1的ib還是B2的ib?d.B1::ib = 1; //正確d.B2::ib = 1; //正確

盡管我們可以通過明確指明調用路徑以消除二義性,但二義性的潛在性還沒有消除,我們可以通過虛繼承來使D類只擁有一個ib實體。

5.虛繼承

虛繼承解決了菱形繼承中派生類擁有多個間接父類實例的情況,虛繼承中派生類的內存布局與普通繼承有很多不同,主要體現在:

虛繼承的子類,如果本身定義新的虛函數,則編譯器會為其生成一個虛函數指針(vptr)以及一張虛函數表,該vptr位于對象內存最前面. 而非虛繼承,直接擴展父類的虛函數表.虛繼承的子類單獨保留了父類的vptr與虛函數表,這部分內容與子類內容以一個四字節的0來分界.虛繼承的子類對象中,含有四字節的虛表指針偏移值.

5.1 虛基類指針

在C++模型中,虛繼承而來的子類會生成一個隱藏的虛基類指針, 虛基類表指針總是在虛函數表指針之后 因而,對某個類實例來說,如果它有虛基類指針,那么虛基類指針可能在實例的0字節偏移處(該類沒有vptr時,vbptr就處于類實例內存布局的最前面,否則vptr處于類實例內存布局的最前面),也可能在類實例的4字節偏移處。 一個類的虛基類指針指向的虛基類表,與虛函數一樣,虛基類表也由多個條目組成,條目中存放的是偏移值,第一個條目存放虛基類表指針(vbptr)所在地址到該類內存首地址的偏移值,由第一段的分析我們知道,這個偏移值為0(類沒有vptr)或者-4(類有虛函數,此時有vptr),我們通過一張圖來更好的理解. 這里寫圖片描述 虛基類表的第二、第三…個條目依次為該類的最左虛繼承父類、次左虛繼承父類…的內存地址相對于虛基類表指針的偏移值,這點我們在下面會驗證。

5.2簡單的虛繼承

//類的內容與前面相同class B{....}class B1:virtual public public B

根據我們前面對虛繼承的派生類的內存布局的分析,B1類的對象模型應該是這樣的 這里寫圖片描述

5.3虛擬菱形繼承

class B{...}class B1: virtual public B{...}class B2: virtual public B{...}class D : public B1,public B2{...}

菱形虛擬繼承下,派生類D類的對象模型又有不同的構成的,在D類對象的內存構成上,有以下幾點:

在D類對象內存中,基類出現的順序是:先是B1(最左父類),然后是B2(次左父類),最后是B(虛祖父類)D類對象的數據成員id放在B類前面,兩部分數據依舊以0來分隔編譯器沒有為D類生成一個它自己的vptr,而是覆蓋并擴展了最左父類的虛基類表,與簡單繼承的對象模型相同.超類B的內容放到了D類對象內存布局的最后。 菱形虛擬繼承下的C++對象模型為: 這里寫圖片描述

6.下面這個空類構成的繼承層次中,每個類的大小是多少?

class B{};class B1 :public virtual B{};class B2 :public virtual B{};class D : public B1, public B2{};int main(){ B b; B1 b1; B2 b2; D d; cout << "sizeof(b)=" << sizeof(b)<<endl; cout << "sizeof(b1)=" << sizeof(b1) << endl; cout << "sizeof(b2)=" << sizeof(b2) << endl; cout << "sizeof(d)=" << sizeof(d) << endl; getchar();}結果:yang@yang:~/C++/對象模型$ ./a.out sizeof(b)=1sizeof(b1)=8sizeof(b2)=8sizeof(d)=16

解析: * 編譯器為空類安插1字節的char,以使該類對象在內存配置一個地址。 * b1虛繼承b,編譯器為其安插8字節的虛基類表指針,此時b1已不為空,編譯器不再為其安插1字節的char. * b2 同理. * d含有來自b1和b2兩個父類的虛基類表指針,大小為16字節.


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 噜噜噜在线 | 免费看黄色一级大片 | 亚洲国产精品久久久久久久 | 日本最新免费二区三区 | 夜夜看| 在线播放91 | 久久99综合久久爱伊人 | 嗯~啊~弄嗯~啊h高潮视频 | 日韩黄a| 人人做人人看 | 一级黄片毛片免费看 | 欧美交在线| 欧美日韩亚洲精品一区二区三区 | 国产一区免费观看 | 92看片淫黄大片欧美看国产片 | 羞羞视频免费网站日本动漫 | 女人a级毛片 | 高清国产午夜精品久久久久久 | 污片视频网站 | 黑色丝袜美美女被躁视频 | 国产免费久久久久 | 午夜国产在线 | 九九综合九九 | 久久新地址 | 天天色图片 | 国产一区精品在线观看 | 久久久国产精品免费观看 | a视频在线播放 | 黄色小视频在线免费看 | 午夜久久视频 | 一区二区免费看 | 欧美性生交xxxxx久久久缅北 | 国产精品久久久久久久久久久久久久久久 | 91久久精品一 | 男女生羞羞视频网站在线观看 | 亚洲一级簧片 | 性欧美极品xxxx欧美一区二区 | 国产1区2区在线观看 | 性 毛片| 成人国产精品一区二区毛片在线 | 黄色av网站在线观看 |