C++对象模型分析(四十三)
我们学习了 C++ 这么长时间了,我们来看看 C++ 中对象的本质。它里面是用 class 定义的对象,class 是一种特殊的 struct。在内存中 class 依旧可以看做变量的集合,class 与 struct 遵循相同的内存对齐规则。class 中的成员函数与成员变量是分开存放的,及每个对象有独立的成员变量,所有对象共享类中的成员函数。那么我们如果在 class 和 struct 中同时定义相同的成员变量的话,它们所占的内存大小会一样嘛?我们来做个实验,代码如下
#include<iostream>usingnamespacestd;classA{inti;intj;charc;doubled;};structB{inti;intj;charc;doubled;};intmain(){cout<<"sizeof(A)="<<sizeof(A)<<endl;cout<<"sizeof(B)="<<sizeof(B)<<endl;return0;}
我们根据之前学的知识可知,sizeof(B) 应该是等于 20 的,我们来看看 sizeof(A) 等于多少呢?
我们看到 A 和 B 所占的内存大小是一样的,那便说明它们的内存分布是相同的。我们下来在 class A 中定义一个 print 函数用来打印几个成员变量的值,再定义 B 类型的指针用来强制转换指向对象 A。再用指针来改变 A 中成员变量的值,具体程序如下
#include<iostream>usingnamespacestd;classA{inti;intj;charc;doubled;public:voidprint(){cout<<"i="<<i<<","<<"j="<<j<<","<<"c="<<c<<","<<"d="<<d<<endl;}};structB{inti;intj;charc;doubled;};intmain(){Aa;cout<<"sizeof(A)="<<sizeof(A)<<endl;cout<<"sizeof(a)="<<sizeof(a)<<endl;cout<<"sizeof(B)="<<sizeof(B)<<endl;a.print();B*p=reinterpret_cast<B*>(&a);p->i=1;p->j=2;p->c='c';p->d=3;a.print();return0;}
那么我们进行强制类型转换后是否可以访问 class 的私有成员变量呢?我们来看看编译结果
我们看到在进行类型转换后,我们可以直接在外部对 class 的成员变量进行直接的改变。在运行时对象会退化位结构体的形式,此时所有的成员变量在内存中一次排布,成员变量间可能存在内存空隙。我们便可以通过内存地址来直接访问成员变量,访问权限的关键字在运行时失效。
类中的成员函数是位于代码段中,调用成员函数时对象地址作为参数隐式传递。成员函数通过对象地址访问成员变量,C++ 语法规则隐藏了对象地址的传递过程。下来我们以代码为例进行分析。
#include<iostream>usingnamespacestd;classDemo{intmi;intmj;public:Demo(inti,intj){mi=i;mj=j;}intgetI(){returnmi;}intgetJ(){returnmj;}intadd(intv){returnmi+mj+v;}};intmain(){Demod(1,2);cout<<"d.i="<<d.getI()<<endl;cout<<"d.j="<<d.getJ()<<endl;cout<<"d.add(3)="<<d.add(3)<<endl;return0;}
我们定义了一个很平常的类,在里面定义了几个返回成员变量的函数,并定义了 一个 add 函数。我们来编译看看
我们看到已经正确实现。那么我们来想想,为什么我们在 getI 函数中能直接返回成员变量 mi 的值呢?是因为在 C++ 中的每个类对象都有一个隐藏的 this 指针,它时刻的指向整个对象,所以才能访问到它中的成员变量。下来我们就用 C 语言来实现上面的 C++ 程序,看看用 C 语言怎么写出面向对象的代码。
class.h 源码
#ifndef_CLASS_H_#define_CLASS_H_typedefvoidDemo;Demo*Demo_Create(inti,intj);intDemo_getI(Demo*pThis);intDemo_getJ(Demo*pThis);intDemo_add(Demo*pThis,intv);voidDemo_Free(Demo*pThis);#endif
class.c 源码
#include"class.h"#include<malloc.h>structClassDemo{intmi;intmj;};Demo*Demo_Create(inti,intj){structClassDemo*ret=(structClassDemo*)malloc(sizeof(structClassDemo));if(ret!=NULL){ret->mi=i;ret->mj=j;}returnret;}intDemo_getI(Demo*pThis){structClassDemo*obj=(structClassDemo*)pThis;returnobj->mi;}intDemo_getJ(Demo*pThis){structClassDemo*obj=(structClassDemo*)pThis;returnobj->mj;}intDemo_add(Demo*pThis,intv){structClassDemo*obj=(structClassDemo*)pThis;returnobj->mi+obj->mj+v;}voidDemo_Free(Demo*pThis){free(pThis);}
test.c 源码
#include<stdio.h>#include"class.h"intmain(){Demo*d=Demo_Create(1,2);//Demod(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);return0;}
我们编译结果如下
我们看到跟它后面的 C++ 代码的效果是一样的,感觉是不是很炫酷呢?下来我们来说说 C++ 中的继承对象模型。在 C++ 编译器的内部类可以理解为结构体,子类是由父类成员叠加子类新成员得到的。如下
下来我们还是以代码为例来进行分析
#include<iostream>usingnamespacestd;classDemo{protected:intmi;intmj;public:voidprint(){cout<<"mi="<<mi<<","<<"mj="<<mj<<endl;}};classDerived:publicDemo{intmk;public:Derived(inti,intj,intk){mi=i;mj=j;mk=k;}voidprint(){cout<<"mi="<<mi<<","<<"mj="<<mj<<","<<"mk="<<mk<<endl;}};structTest{void*p;intmi;intmj;intmk;};intmain(){cout<<"sizeof(Demo)="<<sizeof(Demo)<<endl;cout<<"sizeof(Derived)="<<sizeof(Derived)<<endl;/*Derivedd(1,2,3);Test*p=reinterpret_cast<Test*>(&d);cout<<"Beforechanging..."<<endl;d.print();p->mi=10;p->mj=20;p->mk=30;cout<<"Afterchanging..."<<endl;d.print();*/return0;}
我们先通过打印两个类的大小来看看它们所占的内存大小
分别是 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_typedefvoidDemo;typedefvoidDerived;Demo*Demo_Create(inti,intj);intDemo_getI(Demo*pThis);intDemo_getJ(Demo*pThis);intDemo_add(Demo*pThis,intv);voidDemo_Free(Demo*pThis);Derived*Derived_Create(inti,intj,intk);intDerived_getK(Derived*pThis);intDerived_add(Derived*pThis,intv);#endif
class.c 源码
#include"class.h"#include<malloc.h>staticintDemo_Virtual_Add(Demo*pThis,intv);staticintDerived_Virtual_Add(Derived*pThis,intv);structVTable//2.定义虚函数表数据结构{int(*pAdd)(void*,int);//3.虚函数表里存储的东西};structClassDemo{//1.定义虚函数表指针==>虚函数表指针类型structVTable*vptr;intmi;intmj;};structClassDerived{structClassDemod;intmk;};staticstructVTableg_Demo_vtbl={Demo_Virtual_Add};staticstructVTableg_Derived_vtbl={Derived_Virtual_Add};Demo*Demo_Create(inti,intj){structClassDemo*ret=(structClassDemo*)malloc(sizeof(structClassDemo));if(ret!=NULL){ret->vptr=&g_Demo_vtbl;//4.关联对象和虚函数表ret->mi=i;ret->mj=j;}returnret;}intDemo_getI(Demo*pThis){structClassDemo*obj=(structClassDemo*)pThis;returnobj->mi;}intDemo_getJ(Demo*pThis){structClassDemo*obj=(structClassDemo*)pThis;returnobj->mj;}//6.定义虚函数表中指针所指向的具体函数staticintDemo_Virtual_Add(Demo*pThis,intv){structClassDemo*obj=(structClassDemo*)pThis;returnobj->mi+obj->mj+v;}//5.分析具体虚函数intDemo_add(Demo*pThis,intv){structClassDemo*obj=(structClassDemo*)pThis;returnobj->vptr->pAdd(pThis,v);}voidDemo_Free(Demo*pThis){free(pThis);}Derived*Derived_Create(inti,intj,intk){structClassDerived*ret=(structClassDerived*)malloc(sizeof(structClassDerived));if(ret!=NULL){ret->d.vptr=&g_Derived_vtbl;ret->d.mi=i;ret->d.mj=j;ret->mk=k;}returnret;}intDerived_getK(Derived*pThis){structClassDerived*obj=(structClassDerived*)pThis;returnobj->mk;}staticintDerived_Virtual_Add(Derived*pThis,intv){structClassDerived*obj=(structClassDerived*)pThis;returnobj->mk+v;}intDerived_add(Derived*pThis,intv){structClassDerived*obj=(structClassDerived*)pThis;returnobj->d.vptr->pAdd(pThis,v);}
test.c 源码
#include<stdio.h>#include"class.h"voidrun(Demo*p,intv){intr=Demo_add(p,v);printf("r=%d\n",r);}intmain(){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);return0;}
我们来编译看下是不是和我们在 C++ 中实现的多态的效果是否一致呢?
我们看到它的效果和 C++ 中的多态的效果是一样的,也就是说,我们用 C 语言实现了多态。屌爆了!!通过今天对 C++ 对象模型的分析,总结如下:1、C++ 中的类对象在内存布局上与结构体相同;2、成员变量和成员函数在内存中分开存放;3、访问权限关键字在运行时失效;4、调用成员函数时对象地址作为参数隐式传递;5、继承的本质就是父子间成员变量的叠加;6、C++ 中的多态是通过虚函数表实现的7、虚函数表是由编译器自动生成与维护的,虚函数的调用效率低于普通成员函数。
欢迎大家一起来学习 C++ 语言,可以加我QQ:243343083。
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。