我們學習了 C++ 這么長時間了,我們來看看 C++ 中對象的本質(zhì)。它里面是用 class 定義的對象,class 是一種特殊的 struct。在內(nèi)存中 class 依舊可以看做變量的集合,class 與 struct 遵循相同的內(nèi)存對齊規(guī)則。class 中的成員函數(shù)與成員變量是分開存放的,及每個對象有獨立的成員變量,所有對象共享類中的成員函數(shù)。那么我們?nèi)绻?class 和 struct 中同時定義相同的成員變量的話,它們所占的內(nèi)存大小會一樣嘛?我們來做個實驗,代碼如下
成都創(chuàng)新互聯(lián)公司主要從事網(wǎng)站設(shè)計制作、成都做網(wǎng)站、網(wǎng)頁設(shè)計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)秦都,十多年網(wǎng)站建設(shè)經(jīng)驗,價格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18982081108
#includeusing namespace std; class A { int i; int j; char c; double d; }; struct B { int i; int j; char c; double d; }; int main() { cout << "sizeof(A) = " << sizeof(A) << endl; cout << "sizeof(B) = " << sizeof(B) << endl; return 0; }
我們根據(jù)之前學的知識可知,sizeof(B) 應(yīng)該是等于 20 的,我們來看看 sizeof(A) 等于多少呢?
我們看到 A 和 B 所占的內(nèi)存大小是一樣的,那便說明它們的內(nèi)存分布是相同的。我們下來在 class A 中定義一個 print 函數(shù)用來打印幾個成員變量的值,再定義 B 類型的指針用來強制轉(zhuǎn)換指向?qū)ο?A。再用指針來改變 A 中成員變量的值,具體程序如下
#includeusing namespace std; class A { int i; int j; char c; double d; public: void print() { cout << "i = " << i << ", " << "j = " << j << ", " << "c = " << c << ", " << "d = " << d << endl; } }; struct B { int i; int j; char c; double d; }; int main() { A a; cout << "sizeof(A) = " << sizeof(A) << endl; cout << "sizeof(a) = " << sizeof(a) << endl; cout << "sizeof(B) = " << sizeof(B) << endl; a.print(); B* p = reinterpret_cast(&a); p->i = 1; p->j = 2; p->c = 'c'; p->d = 3; a.print(); return 0; }
那么我們進行強制類型轉(zhuǎn)換后是否可以訪問 class 的私有成員變量呢?我們來看看編譯結(jié)果
我們看到在進行類型轉(zhuǎn)換后,我們可以直接在外部對 class 的成員變量進行直接的改變。在運行時對象會退化位結(jié)構(gòu)體的形式,此時所有的成員變量在內(nèi)存中一次排布,成員變量間可能存在內(nèi)存空隙。我們便可以通過內(nèi)存地址來直接訪問成員變量,訪問權(quán)限的關(guān)鍵字在運行時失效。
類中的成員函數(shù)是位于代碼段中,調(diào)用成員函數(shù)時對象地址作為參數(shù)隱式傳遞。成員函數(shù)通過對象地址訪問成員變量,C++ 語法規(guī)則隱藏了對象地址的傳遞過程。下來我們以代碼為例進行分析。
#includeusing namespace std; class Demo { int mi; int mj; public: Demo(int i, int j) { mi = i; mj = j; } int getI() { return mi; } int getJ() { return mj; } int add(int v) { return mi + mj + v; } }; int main() { Demo d(1, 2); cout << "d.i = " << d.getI() << endl; cout << "d.j = " << d.getJ() << endl; cout << "d.add(3) = " << d.add(3) << endl; return 0; }
我們定義了一個很平常的類,在里面定義了幾個返回成員變量的函數(shù),并定義了 一個 add 函數(shù)。我們來編譯看看
我們看到已經(jīng)正確實現(xiàn)。那么我們來想想,為什么我們在 getI 函數(shù)中能直接返回成員變量 mi 的值呢?是因為在 C++ 中的每個類對象都有一個隱藏的 this 指針,它時刻的指向整個對象,所以才能訪問到它中的成員變量。下來我們就用 C 語言來實現(xiàn)上面的 C++ 程序,看看用 C 語言怎么寫出面向?qū)ο蟮拇a。
class.h 源碼
#ifndef _CLASS_H_ #define _CLASS_H_ typedef void Demo; Demo* Demo_Create(int i, int j); int Demo_getI(Demo* pThis); int Demo_getJ(Demo* pThis); int Demo_add(Demo* pThis, int v); void Demo_Free(Demo* pThis); #endif
class.c 源碼
#include "class.h" #includestruct ClassDemo { int mi; int mj; }; Demo* Demo_Create(int i, int j) { struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo)); if( ret != NULL ) { ret->mi = i; ret->mj = j; } return ret; } int Demo_getI(Demo* pThis) { struct ClassDemo* obj = (struct ClassDemo*)pThis; return obj->mi; } int Demo_getJ(Demo* pThis) { struct ClassDemo* obj = (struct ClassDemo*)pThis; return obj->mj; } int Demo_add(Demo* pThis, int v) { struct ClassDemo* obj = (struct ClassDemo*)pThis; return obj->mi + obj->mj + v; } void Demo_Free(Demo* pThis) { free(pThis); }
test.c 源碼
#include#include "class.h" int main() { Demo* d = Demo_Create(1, 2); // Demo d(1, 2); printf("d.i = %d\n", Demo_getI(d)); // cout << "d.i = " << d.i << endl; printf("d.j = %d\n", Demo_getJ(d)); // cout << "d.j = " << d.j << endl; printf("add(3) = %d\n", Demo_add(d, 3)); // cout << "d.add(3) = " << d.add(3) << endl; Demo_Free(d); return 0; }
我們編譯結(jié)果如下
我們看到跟它后面的 C++ 代碼的效果是一樣的,感覺是不是很炫酷呢?下來我們來說說 C++ 中的繼承對象模型。在 C++ 編譯器的內(nèi)部類可以理解為結(jié)構(gòu)體,子類是由父類成員疊加子類新成員得到的。如下
下來我們還是以代碼為例來進行分析
#includeusing namespace std; class Demo { protected: int mi; int mj; public: void print() { cout << "mi = " << mi << ", " << "mj = " << mj << endl; } }; class Derived : public Demo { int mk; public: Derived(int i, int j, int k) { mi = i; mj = j; mk = k; } void print() { cout << "mi = " << mi << ", " << "mj = " << mj << ", " << "mk = " << mk << endl; } }; struct Test { void* p; int mi; int mj; int mk; }; int main() { cout << "sizeof(Demo) = " << sizeof(Demo) << endl; cout << "sizeof(Derived) = " << sizeof(Derived) << endl; /* Derived d(1, 2, 3); Test* p = reinterpret_cast (&d); cout << "Before changing ..." << endl; d.print(); p->mi = 10; p->mj = 20; p->mk = 30; cout << "After changing ..." << endl; d.print(); */ return 0; }
我們先通過打印兩個類的大小來看看它們所占的內(nèi)存大小
分別是 8 和 12,也和我們之前所分析的是一致的。由于我們重寫了 print 函數(shù),所以我們應(yīng)該將其聲明為虛函數(shù),再加上 virtual 關(guān)鍵字之后再來看看他們的內(nèi)存大小是多少
變成 12 和 16 了,加了 4 個字節(jié)的空間。我們再將注釋掉的內(nèi)容展開,看看結(jié)果
我們通過強制類型轉(zhuǎn)換來改變了他們的成員變量的值。在 struct 結(jié)構(gòu)體中第一個為 void* 的指針,也就是說,在 class 類對象中還有一個指針存在。這個指針便是我們指向虛函數(shù)表的指針。那么 C++ 中多態(tài)究竟是怎么實現(xiàn)的呢?當類中聲明虛函數(shù)時,編譯器會在類中生成一個虛函數(shù)表,虛函數(shù)表是一個存儲成員函數(shù)地址的數(shù)據(jù)結(jié)構(gòu)。虛函數(shù)表是由編譯器自動生成與維護的,virtual 成員函數(shù)會被編譯器放入虛函數(shù)表中。當存在虛函數(shù)時,每個對象都有一個指向虛函數(shù)表的指針。多態(tài)對象模型如下所示
那么在編譯器確認 add 函數(shù)是否為虛函數(shù)時,如果是,編譯器則在對象 VPTR 所指的虛函數(shù)表中查找 add 函數(shù)的地址;如果不是,編譯器則直接可以確定被調(diào)成員函數(shù)的地址。那么我們來看看具體它是怎么調(diào)用的,如下
由此看來,就調(diào)用的效率來說,虛函數(shù)肯定是小于普通成員函數(shù)的。我們再次完善之前用 C 語言實現(xiàn)繼承的代碼,用 C 代碼實現(xiàn)多態(tài)的用法。
class.h 源碼
#ifndef _CLASS_H_ #define _CLASS_H_ typedef void Demo; typedef void Derived; Demo* Demo_Create(int i, int j); int Demo_getI(Demo* pThis); int Demo_getJ(Demo* pThis); int Demo_add(Demo* pThis, int v); void Demo_Free(Demo* pThis); Derived* Derived_Create(int i, int j, int k); int Derived_getK(Derived* pThis); int Derived_add(Derived* pThis, int v); #endif
class.c 源碼
#include "class.h" #includestatic int Demo_Virtual_Add(Demo* pThis, int v); static int Derived_Virtual_Add(Derived* pThis, int v); struct VTable // 2. 定義虛函數(shù)表數(shù)據(jù)結(jié)構(gòu) { int (*pAdd)(void*, int); // 3. 虛函數(shù)表里存儲的東西 }; struct ClassDemo { // 1. 定義虛函數(shù)表指針 ==> 虛函數(shù)表指針類型 struct VTable* vptr; int mi; int mj; }; struct ClassDerived { struct ClassDemo d; int mk; }; static struct VTable g_Demo_vtbl = { Demo_Virtual_Add }; static struct VTable g_Derived_vtbl = { Derived_Virtual_Add }; Demo* Demo_Create(int i, int j) { struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo)); if( ret != NULL ) { ret->vptr = &g_Demo_vtbl; // 4. 關(guān)聯(lián)對象和虛函數(shù)表 ret->mi = i; ret->mj = j; } return ret; } int Demo_getI(Demo* pThis) { struct ClassDemo* obj = (struct ClassDemo*)pThis; return obj->mi; } int Demo_getJ(Demo* pThis) { struct ClassDemo* obj = (struct ClassDemo*)pThis; return obj->mj; } // 6. 定義虛函數(shù)表中指針所指向的具體函數(shù) static int Demo_Virtual_Add(Demo* pThis, int v) { struct ClassDemo* obj = (struct ClassDemo*)pThis; return obj->mi + obj->mj + v; } // 5. 分析具體虛函數(shù) int Demo_add(Demo* pThis, int v) { struct ClassDemo* obj = (struct ClassDemo*)pThis; return obj->vptr->pAdd(pThis, v); } void Demo_Free(Demo* pThis) { free(pThis); } Derived* Derived_Create(int i, int j, int k) { struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived)); if( ret != NULL ) { ret->d.vptr = &g_Derived_vtbl; ret->d.mi = i; ret->d.mj = j; ret->mk = k; } return ret; } int Derived_getK(Derived* pThis) { struct ClassDerived* obj = (struct ClassDerived*)pThis; return obj->mk; } static int Derived_Virtual_Add(Derived* pThis, int v) { struct ClassDerived* obj = (struct ClassDerived*)pThis; return obj->mk + v; } int Derived_add(Derived* pThis, int v) { struct ClassDerived* obj = (struct ClassDerived*)pThis; return obj->d.vptr->pAdd(pThis, v); }
test.c 源碼
#include#include "class.h" void run(Demo* p, int v) { int r = Demo_add(p, v); printf("r = %d\n", r); } int main() { Demo* pb = Demo_Create(1, 2); Derived* pd = Derived_Create(1, 22, 333); printf("pb.add(3) = %d\n", Demo_add(pb, 3)); printf("pd.add(3) = %d\n", Derived_add(pd, 3)); run(pb, 3); run(pd, 3); Demo_Free(pb); Demo_Free(pd); return 0; }
我們來編譯看下是不是和我們在 C++ 中實現(xiàn)的多態(tài)的效果是否一致呢?
我們看到它的效果和 C++ 中的多態(tài)的效果是一樣的,也就是說,我們用 C 語言實現(xiàn)了多態(tài)。屌爆了!!通過今天對 C++ 對象模型的分析,總結(jié)如下:1、C++ 中的類對象在內(nèi)存布局上與結(jié)構(gòu)體相同;2、成員變量和成員函數(shù)在內(nèi)存中分開存放;3、訪問權(quán)限關(guān)鍵字在運行時失效;4、調(diào)用成員函數(shù)時對象地址作為參數(shù)隱式傳遞;5、繼承的本質(zhì)就是父子間成員變量的疊加;6、C++ 中的多態(tài)是通過虛函數(shù)表實現(xiàn)的7、虛函數(shù)表是由編譯器自動生成與維護的,虛函數(shù)的調(diào)用效率低于普通成員函數(shù)。
歡迎大家一起來學習 C++ 語言,可以加我QQ:243343083。