C++对象模型分析(四十三)
我们学习了 C++ 这么长时间了,我们来看看 C++ 中对象的本质。它里面是用 class 定义的对象,class 是一种特殊的 struct。在内存中 class 依旧可以看做变量的集合,class 与 struct 遵循相同的内存对齐规则。class 中的成员函数与成员变量是分开存放的,及每个对象有独立的成员变量,所有对象共享类中的成员函数。那么我们如果在 class 和 struct 中同时定义相同的成员变量的话,它们所占的内存大小会一样嘛?我们来做个实验,代码如下
成都创新互联公司主要从事网站设计制作、成都做网站、网页设计、企业做网站、公司建网站等业务。立足成都服务秦都,十多年网站建设经验,价格优惠、服务专业,欢迎来电咨询建站服务: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; }
我们根据之前学的知识可知,sizeof(B) 应该是等于 20 的,我们来看看 sizeof(A) 等于多少呢?
我们看到 A 和 B 所占的内存大小是一样的,那便说明它们的内存分布是相同的。我们下来在 class A 中定义一个 print 函数用来打印几个成员变量的值,再定义 B 类型的指针用来强制转换指向对象 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; }
那么我们进行强制类型转换后是否可以访问 class 的私有成员变量呢?我们来看看编译结果
我们看到在进行类型转换后,我们可以直接在外部对 class 的成员变量进行直接的改变。在运行时对象会退化位结构体的形式,此时所有的成员变量在内存中一次排布,成员变量间可能存在内存空隙。我们便可以通过内存地址来直接访问成员变量,访问权限的关键字在运行时失效。
类中的成员函数是位于代码段中,调用成员函数时对象地址作为参数隐式传递。成员函数通过对象地址访问成员变量,C++ 语法规则隐藏了对象地址的传递过程。下来我们以代码为例进行分析。
#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; }
我们定义了一个很平常的类,在里面定义了几个返回成员变量的函数,并定义了 一个 add 函数。我们来编译看看
我们看到已经正确实现。那么我们来想想,为什么我们在 getI 函数中能直接返回成员变量 mi 的值呢?是因为在 C++ 中的每个类对象都有一个隐藏的 this 指针,它时刻的指向整个对象,所以才能访问到它中的成员变量。下来我们就用 C 语言来实现上面的 C++ 程序,看看用 C 语言怎么写出面向对象的代码。
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; }
我们编译结果如下
我们看到跟它后面的 C++ 代码的效果是一样的,感觉是不是很炫酷呢?下来我们来说说 C++ 中的继承对象模型。在 C++ 编译器的内部类可以理解为结构体,子类是由父类成员叠加子类新成员得到的。如下
下来我们还是以代码为例来进行分析
#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; }
我们先通过打印两个类的大小来看看它们所占的内存大小
分别是 8 和 12,也和我们之前所分析的是一致的。由于我们重写了 print 函数,所以我们应该将其声明为虚函数,再加上 virtual 关键字之后再来看看他们的内存大小是多少
变成 12 和 16 了,加了 4 个字节的空间。我们再将注释掉的内容展开,看看结果
我们通过强制类型转换来改变了他们的成员变量的值。在 struct 结构体中第一个为 void* 的指针,也就是说,在 class 类对象中还有一个指针存在。这个指针便是我们指向虚函数表的指针。那么 C++ 中多态究竟是怎么实现的呢?当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储成员函数地址的数据结构。虚函数表是由编译器自动生成与维护的,virtual 成员函数会被编译器放入虚函数表中。当存在虚函数时,每个对象都有一个指向虚函数表的指针。多态对象模型如下所示
那么在编译器确认 add 函数是否为虚函数时,如果是,编译器则在对象 VPTR 所指的虚函数表中查找 add 函数的地址;如果不是,编译器则直接可以确定被调成员函数的地址。那么我们来看看具体它是怎么调用的,如下
由此看来,就调用的效率来说,虚函数肯定是小于普通成员函数的。我们再次完善之前用 C 语言实现继承的代码,用 C 代码实现多态的用法。
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. 定义虚函数表数据结构 { int (*pAdd)(void*, int); // 3. 虚函数表里存储的东西 }; struct ClassDemo { // 1. 定义虚函数表指针 ==> 虚函数表指针类型 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. 关联对象和虚函数表 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. 定义虚函数表中指针所指向的具体函数 static int Demo_Virtual_Add(Demo* pThis, int v) { struct ClassDemo* obj = (struct ClassDemo*)pThis; return obj->mi + obj->mj + v; } // 5. 分析具体虚函数 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++ 中实现的多态的效果是否一致呢?
我们看到它的效果和 C++ 中的多态的效果是一样的,也就是说,我们用 C 语言实现了多态。屌爆了!!通过今天对 C++ 对象模型的分析,总结如下:1、C++ 中的类对象在内存布局上与结构体相同;2、成员变量和成员函数在内存中分开存放;3、访问权限关键字在运行时失效;4、调用成员函数时对象地址作为参数隐式传递;5、继承的本质就是父子间成员变量的叠加;6、C++ 中的多态是通过虚函数表实现的7、虚函数表是由编译器自动生成与维护的,虚函数的调用效率低于普通成员函数。
欢迎大家一起来学习 C++ 语言,可以加我QQ:243343083。
当前题目:C++对象模型分析(四十三)
文章地址:http://scjbc.cn/article/picdes.html