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,调用成员函数时对象地址作为参数隐式传递;