继承与多态虚函数分析
来源:互联网 发布:淘宝最新规则在哪里看 编辑:程序博客网 时间:2024/06/01 23:35
此处举例在VS2010中编译:
分析继承与多态虚函数在内存中的分配布局和分析理解过程,直接以多继承举例:
首先说明虚表的概念:虚表即一个类中存放其虚函数地址的表,这个表的地址则存放在此类内存分布中,即一个_vptr的指针;
虚表基本构建:一个类有多个虚函数时,其按照虚函数在类中出现的先后次序填写虚表,最后在虚表后加上0x00000000.
虚表性质:虚表在一个类对象调用构造函数前已经形成,构造函数只是将虚表地址赋给内存的前四个字节,即为指针_vptr.
1.继承中没有虚函数,举例:
class Base1{public:void Func1(){cout<<"Base1"<<endl;}public:int _a;};class Base2{public:void Func2(){cout<<"Base2"<<endl;}public:int _b;};class Derived:public Base1,public Base2{public:void FuncD(){cout<<"Derived"<<endl;}public:int _d;};void Func(){Derived d;d._a=1;d._b=2;d._d=3;int size=sizeof(d);Base1* p1=&d;Base2* p2=&d;Derived* p3=&d;}Derived公有继承Base1,Base2,查看内存:
即p1、p3与&d对象地址相同,p2偏移四个字节,d对象总大小为12,则内存分布为:
2.只将以上Base1与Base2函数前加上virtual,变为虚函数,派生类Derived内容不变,查看内存情况:
Base1:
virtual void Func1(){cout<<"Base1"<<endl;}Base2:
virtual void Func2(){cout<<"Base2"<<endl;}
Derived d;Base1 b1;Base2 b2;
添加两个父类对象,查看内存情况:
b1
b2:
所以当加上virtual关键字后,基类对象各自多增加四个字节,其则为虚表地址_vfptr。再查看继承后的内存分布:
&d地址存储内容如下:
取Base1的_vptr得出虚函数表:
取Base2的_vptr得出虚函数表:
即各自为基类虚函数,并且p1、p3与&d对象地址相同,p2偏移8个字节,d对象总大小为20,所以可以得出内存分布为:
3.在派生类Derived中重写基类中的虚函数Func1()、Func2(),如:
class Derived:public Base1,public Base2{public:virtual void Func1(){cout<<"Derived1"<<endl;}virtual void Func2(){cout<<"Derived2"<<endl;}public:int _d;};此时在查看内存分布:
虚表为:
Base1
Base2
由上可知此时与第二种情况的内存格局的分布没有任何变化,可是它们的区别又是什么呢?查看以下调用:
若去掉刚才基类和派生类关键字virtual或者派生类没有重写基类函数,用父类指针或引用进行调用此函数,运行结果如下:
class Base1{public:virtual void Func1(){cout<<"Base1"<<endl;}public:int _a;};class Base2{public:virtual void Func2(){cout<<"Base2"<<endl;}public:int _b;};class Derived:public Base1,public Base2{public:/*virtual void Func1(){cout<<"Derived1"<<endl;}virtual void Func2(){cout<<"Derived2"<<endl;}*/public:int _d;};void Fun1(Base1* p1){p1->Func1();}void Fun2(Base2* p2){p2->Func2();}void Fun3(Derived* p3){p3->Func1();p3->Func2();}void Func(){Derived d;Fun1(&d);Fun2(&d);Fun3(&d);Base1 b1;Base2 b2;d._a=1;d._b=2;d._d=3;int size=sizeof(d);Base1* p1=&d;Base2* p2=&d;Derived* p3=&d;}
即输出了基类的函数运行结果,若将注释去掉或加上virtual关键字,运行结果如下:
为派生类重写的运行结果。
这时的原因即为C++的多态的另一类运行时多态,动态连编,在运行时确定指针所指的真正对象类型,调用其函数。
这一过程实现的机制则为:第2种情况和第3对象d内存分布格局相同,调用函数运行结果不同,原因在于创建虚表时函数地址发生改变,此时创建虚表的过程为:Derived先后继承Base1,Base2,所以(1)先创建两个基类虚表,按虚函数在基类中出现的先后顺序填写虚表;(2)基类完成后创建派生类虚表,过程先编译器了解基类虚表格式,再了解派生类对基类哪些虚函数进行了重写;(3)有重写的,则将虚表相同位置的函数地址修改为派生类重写的新的虚函数地址;
(4)若派生类有自己的新的虚函数,则将它加在派生类先继承的基类的虚表后,证明如下:
class Derived:public Base1,public Base2{public://派生类重写两个基类虚函数virtual void Func1(){cout<<"Derived1"<<endl;}virtual void Func2(){cout<<"Derived2"<<endl;} //派生类自己虚函数virtual void Func3(){cout<<"Derived3"<<endl;}public:int _d;};查看内存情况
:
内存格局未变,查看虚函数表:
Base1:
Base2
可知Base1虚表增加了一个函数地址,其就为派生类自己的虚函数。
(5)最后在虚表之后加上0X00000000.
4.若派生类在变为虚拟公有继承基类,如:
class Derived:virtual public Base1,virtual public Base2查看内存情况:
此时&d与p3相同,p1偏移12字节,p2偏移20个字节,总大小为28,在查看各地址:
由上内存监视知:即为派生类Derived虚表;
即为Base1虚表;
即为Base2虚表;
可知第二个内存空间存放偏移表地址,偏移表里存放从当前位置开始与自己、两基类的开始位置偏移字节。
内存分布格局如下:
以上即为以多继承为例剖析多态虚函数的内存布局与分析过程。
- 继承与多态虚函数分析
- 继承与构造函数分析
- 虚函数与虚继承内存分析
- C++虚函数多态性的实现与分析+虚继承的实现与分析
- 继承与虚函数
- 继承与虚函数
- 继承与虚函数
- 虚函数与继承
- 继承与构造函数
- 虚函数与继承
- 继承与构造函数
- 虚继承与虚函数继承
- 虚函数继承与虚继承
- 虚函数继承与虚继承
- 虚继承与虚函数继承
- 虚继承与虚函数继承
- 虚继承与虚函数继承
- 多重继承与虚函数
- 字符串个数
- C#使用RenderControl将GridView控件导出到EXCEL的方法
- HRBUST1150-相识
- SQLServer控制用户访问权限表
- PorterDuffXferMode不正确的真正原因PorterDuffXferMode深入试验
- 继承与多态虚函数分析
- iOS开发之
- 坦克大战 【优先队列】
- Linux十个基础知识点
- 在mac下修改mysql密码
- 小程序中使用的第三方库
- Python+Selenium3最新配置
- ZZULI 1190 Faulty Odometer
- jpa 里用的分页实现类org.springframework.data.domain.PageImpl<T>