1,C++ 對象模型:C++ 的對象在內存當中是如何排布的;
1,C++ 對象包含了成員變量和成員函數;
2,本文分析 C++ 對象它的成員變量在內存中如何排布的,C++ 對象它的成員函數在內存中是如何排布的;
2,回歸本質:
1,class 是一種特殊的 struct:
1,class 用來定義類對象,類是一種特殊的結構體,class 這種特殊的結構體特殊在其訪問權限默認為 private,struct 這種結構體定義成員的默認訪問權限是 public,兩者在 C++ 中的區別僅此而已,因此類是一種特殊的結構體這是一種本質;
2,在內存中 class 也就可以看做變量的集合;
3,class 與 struct 遵循相同的內存對齊規則;
1,計算結構體的大小同樣也適用於計算類的大小;
4,class 中的成員函數與成員變量是分開存放的;
1,每個對象有獨立的成員變量;
1,成員變量是數據,數據只可能存放在棧空間(運行時)、堆空間(運行時)、全局數據區這三個區域;
2,所有對象共享類中的成員函數;
1,成員函數是函數,只可能存放在代碼段里;
2,值得思考的問題:
1 class A struct B 2 { { 3 int i; int i; 4 int j; int j; 5 char c; char c; 6 double d; double d; 7 }; }; 8 9 sizeof(A) = ? sizeof(B) = ?
3,對象內存布局初探編程實驗:
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 class A 7 { 8 int i; 9 int j; 10 char c; 11 double d; 12 public: 13 void print() // 雖然寫在一起,但是在內存中的排布是在不同的地方的; 14 { 15 cout << "i = " << i << ", " 16 << "j = " << j << ", " 17 << "c = " << c << ", " 18 << "d = " << d << endl; 19 } 20 }; 21 22 struct B 23 { 24 int i; 25 int j; 26 char c; // 雖然 char 類型大小為 1 個字節,但是由於內存對齊,其大小為 4 個字節,其中三個字節是內存空隙; 27 double d; 28 }; 29 30 int main() 31 { 32 A a; 33 34 cout << "sizeof(A) = " << sizeof(A) << endl; // 20 bytes,數據類型本質就是固定內存空間大小的別名; 35 cout << "sizeof(a) = " << sizeof(a) << endl; // 20 bytes,C++ 對象中並沒有包含成員函數,其僅僅代表成員變量的集合; 36 cout << "sizeof(B) = " << sizeof(B) << endl; // 20 bytes 37 38 a.print(); // 打印亂碼 39 40 B* p = reinterpret_cast<B*>(&a); // 用 reinterpret_cast 重解釋 a 對象所代表的這一段內存; 41 42 /* 將 a 對象當做一個結構體 B 的變量來使用;由於 a 里面的成員變量在內存空間里面的排布與 struct B 在內存空間里面的排布是一樣的,所有就可以通過下列方式就可以修改 a 對象里面的私有成員 */ 43 p->i = 1; 44 p->j = 2; 45 p->c = 'c'; 46 p->d = 3; 47 48 a.print(); // 打印 i = 1, j = 2, c = c, d = 3;這里結果說明一個對象真的就是一個結構體;可以將一個對象當做一個結構體來使用; 49 50 p->i = 100; 51 p->j = 200; 52 p->c = 'C'; 53 p->d = 3.14; 54 55 a.print(); // 打印 i = 100, j = 200, c = C, d = 3.14,不是巧合 56 57 return 0; 58 }
4,C++ 對象模型分析:
1,運行時的對象退化為結構體的形式:
1,所有成員變量在內存中依次排布;
1,像結構體中的一樣依次排布;
2,所有變量間可能存在內存空隙;
1,內存對齊;
3,可以通過內存地址直接訪問成員變量;
1,地址賦值給指針;
4,訪問權限關鍵字在運行時失效;
1,private 和 protected 的訪問權限僅在編譯時候有效;
2,成員函數運行方式:
1,類中成員函數位於代碼段中;
2,調動成員函數時對象地址作為參數隱式傳遞;
1,對象通過點操作符調用成員函數,點操作符的背后,編譯器將對象的地址傳遞到了成員函數的內部;
2,成員函數為何能夠訪問(無權限)成員變量,在內存中是分開存放的,有什么理由成員函數內部就可以直接訪問到對象的成員變量 呢?
3,就是由於這個隱藏了的地址傳遞過程,C++ 編譯器在編譯成員函數調用的時候,隱藏的傳遞了對象的地址,在成員函數的內部有了這個隱藏的地址當然可以直接訪問對象的成員變量,這就很好的解釋了 this指針,這個指針只能在成員函數內部使用,其代表當前對象,並保存了對象的地址;
3,成員函數通過對象地址訪問成員變量;
4,C++ 語法規則隱藏了對象地址的傳遞過程;
5,對象本質分析編程實驗:
1,C++ 程序;
1 #include <iostream> 2 #include <string> 3 4 using namespace std; 5 6 class Demo 7 { 8 int mi; 9 int mj; 10 public: 11 Demo(int i, int j) 12 { 13 mi = i; 14 mj = j; 15 } 16 17 int getI() 18 { 19 return mi; 20 } 21 22 int getJ() 23 { 24 return mj; 25 } 26 27 int add(int value) 28 { 29 return mi + mj + value; 30 } 31 }; 32 33 int main() 34 { 35 Demo d(1, 2); 36 37 cout << "sizeof(d) = " << sizeof(d) << endl; // 8 bites; 38 cout << "d.getI() = " << d.getI() << endl; // 1; 39 cout << "d.getJ() = " << d.getJ() << endl; // 2; 40 cout << "d.add(3) = " << d.add(3) << endl; // 6; 41 42 return 0; 43 }
2,用 C 語言完成上述面向對象程序:
1,50-2.h 頭文件:
1 #ifndef _50_2_H_ 2 #define _50_2_H_ 3 4 typedef void Demo; // 定義一個新的類型(作為類類型,這里能看到的也只有這個類型); 5 6 Demo* Demo_Create(int i, int j); // 作為構造函數看待;C++ 中之所以沒有返回值,是因為已經存在了; 7 int Demo_GetI(Demo* pThis); // 地址的傳遞過程,用 pThis 實現; 8 int Demo_GetJ(Demo* pThis); 9 int Demo_Add(Demo* pThis, int value); 10 void Demo_Free(Demo* pThis); // C 中編譯器不可能提供析構函數,要自己定義;C++ 中之所以沒有返回值,是因為已經存在了; 11 12 #endif
2,50-2.c 實現文件:
1 #include "50-2.h" 2 #include "malloc.h" 3 4 /* 結構體代表真正的類,反映了 class 這個類的本質就是結構體 */ 5 struct ClassDemo 6 { 7 int mi; 8 int mj; 9 }; 10 11 /* 實現構造函數 */ 12 /* 其實我們實實在在的定義了這個結構體,但是對於用戶而言,根本沒有必要知道這個類是如何實現的,對於一個類的用戶而言,只用知道如何使用成員函數就可以了 */ 13 Demo* Demo_Create(int i, int j) // 構造函數沒有 this 指針; 14 { 15 struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo)); // C 中需要加 struct; 16 17 if( ret != NULL ) 18 { 19 ret->mi = i; 20 ret->mj = j; 21 } 22 23 return ret; 24 } 25 26 int Demo_GetI(Demo* pThis) // pThis 代表當前對象 27 { 28 struct ClassDemo* obj = (struct ClassDemo*)pThis; 29 30 return obj->mi; 31 } 32 33 int Demo_GetJ(Demo* pThis) 34 { 35 struct ClassDemo* obj = (struct ClassDemo*)pThis; 36 37 return obj->mj; 38 } 39 40 int Demo_Add(Demo* pThis, int value) 41 { 42 struct ClassDemo* obj = (struct ClassDemo*)pThis; 43 44 return obj->mi + obj->mj + value; 45 } 46 47 void Demo_Free(Demo* pThis) 48 { 49 free(pThis); 50 }
3,使用文件:
1 #include <stdio.h> 2 #include "50-2.h" 3 4 int main() 5 { 6 Demo* d = Demo_Create(1, 2); // d 代表一個指針,指向一個對象;等價的 C++ 中的代碼為 Demo* d = new Demo(1, 2); 7 8 printf("d.mi = %d\n", Demo_GetI(d)); // d->getI(); 9 printf("d.mj = %d\n", Demo_GetJ(d)); // d->getJ(); 10 printf("Add(3) = %d\n", Demo_Add(d, 3)); // d->add(3); 11 12 // d->mi = 100; // 1,warning: dereferencing 'void *' pointer (正在間接引用空指針);error: request for member 'mi' in something not a structure or union (在非結構體和聯合體中訪問成員 mi) ; 13 // 2,面向對象的觀點來看,mi 是私有的,類的外部不可以訪問,只能通過成員函數訪問,這是面向對象中的信息隱藏; 14 // 3,在 C 語言中,沒有 private 關鍵字,因此我們只能用 typedef void Demo 這樣的技術,通過定義一個 void 的 Demo 來進行信息隱藏,進行 private 的模擬; 15 16 Demo_Free(d); 17 18 return 0; 19 }
1,這個程序告訴我們,面向對象它不是 C++ 專屬,我們依然可以用 C 語言寫面向對象;
2,更深層的就是,C++ 編譯器在背后做了很多的事情,我們有必要了解這個事情是如何發生的;
3,對於一個類而言,成員變量和成員函數是分開存放的,這一點是非常重要, 如果掌握了這個本質,我們用任何語言,都可以寫出面向對象的代碼;
6,小結:
1,C++ 中的類對象在內存布局上與結構體相同;
1,本質就是:class 是一種特殊的 struct;
2,成員變量和成員函數在內存中分開存放;
3,訪問權限關鍵字在運行時失效;
4,調用成員函數時對象地址作為參數隱式傳遞;