第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版权协议,转载请附上原文出处链接和本声明. ...
随机推荐
- Python语言规范及风格规范
语言规范: http://zh-google-styleguide.readthedocs.io/en/latest/google-python-styleguide/python_language_ ...
- Eclipse下Android开发的问题:Failed to install AndroidPhone.apk on device 'emulator-5554': timeout 解决办法
在window->preferences->Android->DDMS->ADB connection time out (ms): 将这个值设置的大一些,默认为5000,我设 ...
- 【GOF23设计模式】组合模式
来源:http://www.bjsxt.com/ 一.[GOF23设计模式]_组合模式.树状结构.杀毒软件架构.JUnite底层架构.常见开发场景 package com.test.composite ...
- [js开源组件开发]js轮播图片支持手机滑动切换
js轮播图片支持手机滑动切换 carousel-image 轮播图片,支持触摸滑动. 例子见DEMO http://www.lovewebgames.com/jsmodule/carousel-ima ...
- Eclipse反编译工具Jad及插件JadClipse配置
Jad是一个Java的一个反编译工具,是用命令行执行,和通常JDK自带的java,javac命令是一样的.不过因为是控制台运行,所以用起来不太方便.不过幸好有一个eclipse的插件JadClipse ...
- javascript数组浅谈3
前两节说了数组最基本的创建,队列方法,排序和一些操作方法,这节说说迭代和归并方法. every()方法 & some()方法 这两个方法会对数组中的每一项运行给定函数,然后返回一个布尔值,理解 ...
- ORA-00257归档日志写满的解决方法
背景: 在前一篇博客中我们提到了如何启动或关闭oracle的归档(ARCHIVELOG)模式,在我成功设定数据库为归档模式以后, 第二天再次尝试连接数据库,报错:ORA-00257.在网上找到了一圈资 ...
- (方法调配)Method Swizzling
一.概念 方法调配:因为Objective-C是运行时语言,也就是说究竟会调用何种方法要在运行期才能解析出来.那么我们其实也可以在运行时改变选择子名称.这样我们既不需要查看到源代码,又没有必要去重写子 ...
- linux下安装mysql手记
安装mysql 下载mysql-standard-4.1.8-pc-linux-i686.tar.gz文件到目录/usr/local/下 # groupadd mysql //添加mysql用户组 ...
- python数据结构-列表-建立/索引/反转