第51课 C++对象模型分析(下)
1. 单继承对象模型
(1)单一继承
【编程实验】继承对象模型初探
#include <iostream> using namespace std; class Demo { protected: int mi; int mj; public: //虚函数 virtual 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; //12,不是8,因为插入了一个虚函数表指针 cout << "sizeof(Derived) = " << sizeof(Derived) << endl; //16,不是12,原因同上 Derived d(, , ); Test* p = reinterpret_cast<Test*>(&d); cout << endl; //以下实验证明带有虚函数的Derived的内存模型与Test结构体是一致的 //1、大小相同。2、第1个成员变量是vptr指针;3、往后依次为mi、mj、mk cout << "Before Change..." << endl; d.print(); p->mi = ; p->mj = ; p->mk = ; cout << "After Change..." << endl; d.print(); ; } /*输出结果: sizeof(Demo) = 12 sizeof(Derived) = 16 Before Change... mi = 1, mj = 2, mk = 3 After Change... mi = 10, mj = 20, mk = 30 */
(2)Derived对象的内存布局
【实例分析】单一继承
class Base { public: Base() { mBase1 = ; mBase2 = ; } virtual void func1() { cout << "Base::func1()" << endl; } virtual void func2() { cout << "Base::func2()" << endl; } private: int mBase1; int mBase2; }; class Derived : public Base { public: Derived(): Base() { mDerived1 = ; mDerived2 = ; } virtual void func2() { cout << "Derived::func2()" << endl; } virtual void func3() { cout << "Derived::func3()" << endl; } private: int mDerived1; int mDerived2; };
(3)结论
①vptr位于对象的最前端,非static的成员量根据其继承顺序和声明顺序排在其后。
②子类继承基类所声明的虚函数,即基类的虚函数地址会被复制到派生类的虚函数表中相应的项中。(即子类有自己一张独立的虚函数表)
③子类中新加入的virtual函数跟在其继承而来的virtual后面。如本例中的的func3虚函数被添加到func2后面。
④若子类重写父类的virtual函数,则子类的虚函数表中该virtual函数对应的项会更新为新函数的地址。如本例中,子类重写func2虚函数,则虚函数表中的func2的项更新为子类重写的函数func2的地址。
2.多重继承对象模型
(1)多重继承
【实例分析】多重继承
class Base1 { public: Base1() { mBase1 = ; } virtual void funcA() { cout << "Base1::funcA()" << endl; } virtual void funcB() { cout << "Base1::funcB()" << endl; } private: int mBase1; }; class Base2 { public: Base2() { mBase2 = ; } virtual void funcA() { cout << "Base2::funcA()" << endl; } virtual void funcC() { cout << "Base2::funcC()" << endl; } private: int mBase2; }; class Derived : public Base1, public Base2 { public: Derived(): Base1(), Base2() { mDerived = ; } virtual void funcD() { cout << "Derived::funcD()" << endl; } virtual void funcA() { cout << "Derived::funcA()" << endl; } private: int mDerived; };
(2)Derived对象的内存布局
(3)结论
①n重继承下,子类会有n张虚函数表。其中1个为主表,与第1个基类(如本例中的Base1)共享,其他为次表,与其他基类(如本例中的Base2)有关
②子类新声明的virtual函数,放在主虚函数表中。如本例中,子类新声明的与Base共享虚函数表。
③每一个父类的对象在子类的对象保持原样,并依次按声明次序排列。
④若子类重写virtual函数,则其所有父类中的签名相同的virtual函数会被改写。如本例中,子类重写了funcA函数,则两个虚函数表中的funcA函数的项均被更新为子类重写的函数的地址。这样做的目的是为了解决不同的父类类型的指针指向同一个子类对象,而能够调用到实际的函数。
3. 关于虚析构函数的说明
(1)若父类声明了一个virtual析构函数,则其子类的析构函数会更新其所有的虚函数表中的析构函数的项,把该项中的函数地址更新为子类的析构函数的地址。
(2)因为当父类的析构函数为virtual时,若用户不显式提供一个析构函数,编译器会自动合成一个,所以若父类声明了一个virtual析构函数,其子类中必然存在一个virtual的析构函数,并用这个virtual析构函数更新虚函数表。
4. 多态的原理
(1)多态:使用父类指针(或引用)时调用虚函数时,会产生多态
Base* p; …… p->vfunc(); //vfunc是Base中声明的virtual函数
(2)多态原理:
①由于指针p可以指向一个Base对象,也可以指向其派生类的对象,而编译器在编译时并不知道p所指向的真实对象到底是什么,那么究竟如何判断呢?
②从C++对象的内存分布图中可以看出,尽管虚函数表的地址可能被更新,但在父类与子类间相同签名的虚函数在虚函数表中的索引值是不会变的。所以无论p指向的是Base对象,还是其派生类的对象,其virtual函数vfunc在虚函数表中的索引值是不变的(设均为1)
③在编译期,编译器无法知道具体的对象,但可以根据指针p所指向的对象的Base子对象(即,子类与父类重合的那部分内存)中虚函数表来实现函数调用。于是编译器可能把virtual函数调用的代码修改为如下的伪代码:
(*p->vptr[])(p); //假设vfunc函数在虚函数表中的索引值为1, //参数p为this指针,因为成员函数默认都要传入this指针。
(3)实现多态:
①若p指向一个Base对象,则调用父类Base本身的虚函数表中索引值为1的函数。
②若p指向一个Base派生类对象,则调用子类自身中虚函数表中索引值为1的函数,这样就实现了多态。(注意父类和子类的虚函数表是相互独立的,只不过子类会父类中复制一部分过来而己)
③这种函数调用是根据指针p所指的对象的虚函数表来实现的,在编译时由于无法确定指针p所指的真实对象,所以无法确定真实要调用哪一个函数,只有在运行时根据指针p所指的对象来动态决定。所以说,虚函数是在运行时动态绑定的,而不是在编译时静态绑定的。
(4)小结
①当类中声明虚函数时,编译器会在类中生成一个虚函数表,该表是一个存储成员函数(虚函数)地址的数据结构。
②虚函数表是由编译器自动生成与维护的
③virtual成员函数会被放入虚函数表中。
④存在虚函数时,每个对象都有一个指向虚函数表的指针
【编程实验】多态本质分析——用C写面向对象
//51-2.h
#ifndef _51_2_H_ #define _51_2_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 value); 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 value); #endif
//51-2.c
#include "51-2.h" #include <malloc.h> static int Demo_Virtual_Add(Demo* pThis, int value); static int Derived_Virtual_Add(Demo* pThis, int value); struct VTable //2. 定义虚函数表数据结构 { int (*pAdd)(void*, int); //3. 虚函数表里面存储的内容(函数指针) }; struct ClassDemo { struct VTable* vptr; //1. 定义虚函数表指针 ==>虚函数表指针的类型??? 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)); ) { 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 value) { struct ClassDemo* obj = (struct ClassDemo*)pThis; return obj->mi + obj->mj + value; } //5. 分析具体的虚函数 int Demo_Add(Demo* pThis, int value) //是个虚函数 { struct ClassDemo* obj = (struct ClassDemo*)pThis; //从虚函数表中找到真正的实现函数 return obj->vptr->pAdd(pThis, value); } void Demo_Free(Demo* pThis) { free(pThis); } //**********************************Derived类**************************** Derived* Derived_Create(int i, int j, int k) { struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived)); ) { 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(Demo* pThis, int value) { struct ClassDerived* obj = (struct ClassDerived*)pThis; return obj->mk + value; } //分析虚函数 int Derived_Add(Derived* pThis, int value) { struct ClassDerived* obj = (struct ClassDerived*)pThis; return obj->d.vptr->pAdd(pThis, value); }
//main.c
#include <stdio.h> #include "51-2.h" void run(Demo* p, int v) { ); //多态 printf("r = %d\n", r); } int main(void) { Demo* pb = Demo_Create(, ); Derived* pd = Derived_Create(, , ); printf()); printf()); run(pb, ); run(pd, ); Demo_Free(pb); Demo_Free(pd); ; }
5. 小结
(1)继承的本质是父子间成员变量的叠加
(2)C++中的多态是通过虚函数表实现的
(3)虚函数表是由编译器自动生成与维护的
(4)虚函数的调用效率低于普通成员函数。
【参考资料】:C++对象模型之详述C++对象的内存布局
第51课 C++对象模型分析(下)的更多相关文章
- 第50课 C++对象模型分析(上)
1. 回归本质 (1)class是一种特殊的结构体 ①在内存中class依旧可以看作变量的集合 ②class与struct遵循相同的内存对齐规则 ③class中的成员函数与成员变量是分开存放的.即每个 ...
- 第50 课C++对象模型分析——成员变量(上)
C++对象模型,其实就是C++中的对象在内存中是如何排布的.C++中的对象包含了成员变量和成员函数,其实就是研究C++中的类对象它的成员变量和成员函数在内存中是如何排布的. 回归本质class 是一种 ...
- 第50 课C++对象模型分析——成员函数(上)
类中的成员函数位于代码段中调用成员函数时对象地址作为参数隐式传递成员函数通过对象地址访问成员变量C++语法规则隐藏了对象地址的传递过程 #include<iostream> #includ ...
- 【转】证书的应用之一 —— TCP&SSL通信实例及协议分析(下)
原文链接 前面两部分分别讲解了如何在.net程序中使用SSL实现安全通信以及SSL的通信过程,并通过抓包工具具体分析了ssl的握手过程,本文通过一个demo来模拟ssl协议,在TCP之上实现自己的安全 ...
- 第24课 - #pragma 使用分析
第24课 - #pragma 使用分析 1. #pragma简介 (1)#pragma 是一条预处理器指令 (2)#pragma 指令比较依赖于具体的编译器,在不同的编译器之间不具有可移植性,表现为两 ...
- 用Python分析下王小波与李银河写情书最爱用哪些词
作家王小波其实也是我国最早期的程序员,突发奇想,王小波写情书最喜欢用哪些词呢?用Python词云分析下! 直接上代码吧,有注释很好理解.输出的图片设置的比较大,所以运行的比较慢,可以适当把图片尺寸改小 ...
- TreeMap 还能排序?分析下源码就明白了
Java 中的 Map 是一种键值对映射,又被称为符号表或字典的数据结构,通常使用哈希表来实现,但也可使用二叉查找树.红黑树实现. HashMap 基于哈希表,但迭代时不是插入顺序 LinkedHas ...
- TIOBE11月份编程语言排行榜:C非常接近Java,分析下中美的就业情况
TIOBE公布11月份编程语言排行榜:C非常接近Java Swift挤进前10,分析下中美的就业情况. 我们先看看他们官方对数据的解读 本月TIOBE指数前20位出现了一些有趣的变动.首先,C语言现在 ...
- Elasticsearch7.X 入门学习第九课笔记-----聚合分析Aggregation
原文:Elasticsearch7.X 入门学习第九课笔记-----聚合分析Aggregation 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. ...
随机推荐
- 成熟的RosettaNet解决方案软件介绍
RosettaNet是一套B2B标准,以标准来优化供应链管理流程,它可以缩短整个供应链各个供货周期.RosettaNet 标准为电子商务标准化提供一个健壮的.非专有的解决方案,它是免费的,通过 Ros ...
- 安全协议:SSL、TSL、SSH概述
SSL(Secure Socket Layer--安全套接字层):为网络通信安全以及数据完整性提供保障的一种安全协议,在TCP/IP的传输层对网络连接进行加密: TSL(Transport Layer ...
- Afinal
1.注解功能 1)继承:FinalActivity ( 需要复制 afinal_0.5.1_bin.jar到lib下) 2)@ViewInject() public class AfinalActiv ...
- Mysql优化经验
一.索引优化 范围匹配使用B-tree索引 等值匹配使用 HASH索引,hash所有唯一Memory引擎 2.索引三星系统, 1.相关记录放到一起 2.索引中的数据和查找中的排序顺序一直 3.索引的 ...
- javascript宿主对象之window.navigator
window.navigator用来反映浏览器及其功能信息的对象. // 检测浏览器版本信息 function getBrowserInfo(){ var Sys = {}; var ua = win ...
- ASP.NET获取请求的url信息汇总
ASP.NET获取请求的url信息汇总 最近做项目需要处理一个用代码获取当前网站的域名或ip信息的问题,于是尝试了ASP.NET中各种获取url信息的方法,在此总结一下: 在Global.asax文件 ...
- Oracle执行计划与统计信息的一些总结
[日期:2011-08-05]来源:Linux社区 作者:wangshengfeng1986211[字体:大 中 小] 2010-07-01 15:03 1.SET AUTOTRACE ON EXP ...
- SharePoint 2013 术语和术语集介绍
托管元数据是一个集中管理的术语的分层集合,我们可以定义术语和术语集,然后将其用作 SharePoint Server 2013 中项目的属性.简单的说,术语是一个可与 SharePoint Serve ...
- SharePoint 错误集 3
1. workflow 流程走不下去,报 workflow fails to run 的错误 请确保下面二个service要么都start,要么都stop: Microsoft SharePoint ...
- 在Seismic.NET下用最少的语句写出一个剖面显示程序
用Seismic.NET开发地震剖面显示程序可以节省大量的时间,下面的代码展开了如何用最少的代码显示一个SEGY文件. // 用一行语句把 reader, pipeline, view 和 plot ...