C++内存中的封装、继承、多态(上)

继我的上一篇文章:浅谈学习C++时用到的【封装继承多态】三个概念

此篇我们从C++对象内存布局和构造过程来具体分析C++中的封装、继承、多态。

一、封装模型的内存布局

常见类对象的成员可能包含以下元素:内建类型、指针、引用、组合对象、虚函数。

另一个角度的分类:

数据成员:静态、非静态

成员函数:静态、非静态、虚函数

1.仅包含内建类型的场合:

class T
{
int data1;
char data2;
double data3;
};

类中的内建类型按照声明的顺序在内存中连续存储,并且分配的大小由内建类型本身的大小决定(依赖机器),布局受字节对齐影响(本篇不讨论字节对齐)

2.包含指针和引用的场合:

class T
{
int data1;
char data2;
double data3;
int& ri1;//需要构造函数
int* rp1;
int (*pf)();
};

存储方式同1的场合,不同点为指针和引用通常为固定大小(32位机器4字节、64位机器8字节)。

有关引用:个人理解的引用就是懒人专用指针,取地址又间地址是很麻烦的操作,于是出现了自动取址又间址的指向常量的常指针

在类中声明可以测出固定字节大小,所以也是占用固定的字节大小。

3.包含组合对象的场合:

class Q
{
int a;
int b;
}; class T
{int data1;
Q q;
double  data2;
};

内存布局图示(本篇以及后续篇使用的环境为 32位Win7, VS2008):

再来看一下地址:

结论:(显而易见就不解释了)

类对象最终被解释成内建类型,布局依然按照声明的顺序,并且对象布局在内存中依然是连续的

4.在3的场合添加虚函数的场合

class Q
{
virtual void fun(){}
int a;
int b;
}; class T
{
virtual void fun(){}
int data1;
Q q;
double data2;
};

内存布局图示

通过程序输出看一下

typedef void (*PF)();

int main()
{
T t;
PF pf1, pf2; cout<<"vfptr的地址"<<(int*)&t<<endl;
cout<<"vftable的地址"<<(int*)*(int*)&t<<endl;
cout<<"通过vftable调用类T的fun函数: ";
pf1 = (PF)*(int*)*(int*)&t;
pf1(); cout<<"通过vftable调用类Q的fun函数: ";
pf2 = (PF)*(int*)*(int*)&t.q;
pf2(); return 0;
}

输出图示

推理证明:

1.取t的地址强转成(int*)类型输出以后得到的地址 == 取t的vfptr的地址(调试窗口第一行): 虚函数指针被放在对象布局的首地址位置

2.因为(int*)&t == vfptr,那么*vfptr得到的是虚函数表的首地址。

(int*)*vfptr,把虚函数表的首地址强转成(int*)的地址 == t对象的__vftable的虚函数表的地址(调试窗口第四行行):虚函数指针指向虚函数表

3.vftable的首地址到vftable的第一个函数的地址中间相差很多空间:虚函数表还承担了虚函数以外的内容

什么内容也会放在虚函数表中呢?

虚函数表用来实现多态,多态意味着类型上的模糊,模糊以后必须有东西来记录自己的老本,否则无法实现另外一个东西——RTTI

结论:

在包含虚函数的场合多了一个vfptr,它是一个const指针,位于类布局中的首位置,指向了虚函数表,虚函数表包含了虚函数地址,通过虚函数地址访问虚函数。

并且虚函数表的首地址存在了本类的类型信息,用于实现RTTI。

5.包含了static的场合

static的特性众所周知,从调试窗口观察变量并不能得出什么结论,我们先列出几条特性:

1.static成员为整个类共有的属性

2.static函数不包含this指针

3.static成员不能访问nonstatic成员

初步结论:

内存对象模型中对static作了隔离处理(不是所有对象具有的),static自己独霸一方。

通过以上5条现在来构建C++的封装模型:

有关普通的成员函数

所谓类,就是自己圈定了一个域名,所以在内存中的代码区也圈定了自己的域,普通的成员函数放在那里。

有关静态成员函数

在代码区中圈定的类域名中的圈定一个static区域,思路依然是独霸一方。

有关构造函数

由于构造函数的特殊性,所以在代码区拥有一个自己的构造代码区域。

现在又有了一个更完整的模型:

假定读者已经了解堆/栈/静态区和常量区/代码区

根据上图我们得到一些结论

1.类最终被解释内建类型(内建类型过了编译期以后,都不复存在,只是编译期的解读方式而已)

2.内建类型按照声明的次序顺序存储

3.存在虚函数的场合,会生成vfptr,并且vfptr->vtable->function()

4.静态成员被单独对待、数据只有一份拷贝,函数被放到static区域。

5.Type Infomation被放到vftable中

二、封装模型的构造过程

1.静态是编译期决定的,所有对象共有的数据拷贝,优先创建。

2.进入构造函数,优先创建vfptr和vftable,也就是优先构造虚函数部分

3.其次按照声明的顺序构造数据成员。

我们可以使用逗号表达式来干一些有意思的事情。

事先我们需要定义

typedef void (*PF)();
PF pf = NULL;

class Q
{
public:
Q():b((cout<<"b constructing\n", 1)), a((cout<<"a constructing\n", 2)){}//组合对象的初始化顺序,注意初始化列表写的顺序是和声明的顺序相反的 virtual void fun(){cout<<"Q::f"<<endl;}
int a;
int b;
}; class T
{
public:
T():data1(((pf =(PF)*(int*)*(int*)this, pf()), cout<<"data1 constructing\n", 1)), data2((cout<<"data2 constructing\n", 2)){}//data2的构造使用了简单的逗号表达式
  //data1的初始化嵌套了一层逗号表达式,结构其实是data1((为函数指针pf赋值, 调用pf), 打印data1构造中, 数值)
virtual void fun(){cout<<"T::f"<<endl;}
int data1;
Q q;
double data2;
static int sdata1;
}; int T::sdata1 = (cout<<"sdata1 constructing\n", 10);//用来指定静态变量的初始化顺序

以下是程序运行的结果:

静态--虚函数表--声明次序初始化。

文章不免有疏忽和不足的地方,欢迎大家批评指正。邮箱【betachen@yeah.net】

下一篇重点讲继承时和多态时的内存布局。

C++内存中的封装、继承、多态(上)的更多相关文章

  1. C++内存中的封装、继承、多态(下)

    上篇讲述了内存中的封装模型,下篇我们讲述一下继承和多态. 二.继承与多态情况下的内存布局 由于继承下的内存布局以及构造过程很多书籍都讲得比较详细,所以这里不细讲.重点讲多态. 继承有以下这几种情况: ...

  2. java面向对象(封装-继承-多态)

    框架图 理解面向对象 面向对象是相对面向过程而言 面向对象和面向过程都是一种思想 面向过程强调的是功能行为 面向对象将功能封装进对象,强调具备了功能的对象. 面向对象是基于面向过程的. 面向对象的特点 ...

  3. 浅谈学习C++时用到的【封装继承多态】三个概念

    封装继承多态这三个概念不是C++特有的,而是所有OOP具有的特性. 由于C++语言支持这三个特性,所以学习C++时不可避免的要理解这些概念. 而在大部分C++教材中这些概念是作为铺垫,接下来就花大部分 ...

  4. Java三大特性(封装,继承,多态)

    Java中有三大特性,分别是封装继承多态,其理念十分抽象,并且是层层深入式的. 一.封装 概念:封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别:将抽象得到的数据 ...

  5. Java基础——面向对象(封装——继承——多态 )

    对象 对象: 是类的实例(实现世界中 真 实存在的一切事物 可以称为对象) 类: 类是对象的抽象描述 步骤: 1.定义一个类 (用于 描述人:) ( * 人:有特征和行为) 2.根据类 创建对象 -- ...

  6. Python 入门 之 面向对象的三大特性(封装 / 继承 / 多态)

    Python 入门 之 面向对象的三大特性(封装 / 继承 / 多态) 1.面向对象的三大特性: (1)继承 ​ 继承是一种创建新类的方式,在Python中,新建的类可以继承一个或多个父类,父类又可以 ...

  7. php面向对象 封装继承多态 接口、重载、抽象类、最终类总结

    1.面向对象 封装继承多态  接口.重载.抽象类.最终类 面向对象 封装继承多态  首先,在解释面向对象之前先解释下什么是面向对象? [面向对象]1.什么是类? 具有相同属性(特征)和方法(行为)的一 ...

  8. OOP三大核心封装继承多态

    OOP支柱 3 个核心:封装 继承 多态 封装就是将实现细节隐藏起来,也起到了数据保护的作用. 继承就是基于已有类来创建新类可以继承基类的核心功能. 在继承中 另外一种代码重用是:包含/委托,这种重用 ...

  9. python面向对象(封装,继承,多态)

    python面向对象(封装,继承,多态) 学习完本篇,你将会深入掌握 如何封装一个优雅的借口 python是如何实现继承 python的多态 封装 含义: 1.把对象的属性和方法结合成一个独立的单位, ...

随机推荐

  1. 抓取数据同步备份hive

    1:创建表 CREATE external TABLE `tbl_spider`( `url` string, `html` string ) partitioned by ( `site` stri ...

  2. OpenVPN多实例优化的思考过程

    1.sss 当构建组件之间的关系已经错综复杂到接近于一张全然图的时候,就要换一个思路了,或者你须要重构整个系统,或者你将又一次实现一个. 2.TAP网卡和TUN网卡 2.1.TAP的优势 1.方便组网 ...

  3. Hack 语言学习/参考---1.Hack 语言

    Table of Contents What is Hack? Hack Background Summary Hack is a language for HHVM that interopates ...

  4. WinForm播放视频

    原文:WinForm播放视频 1背景 这几天一老友要求我做个小软件,在WinForm播放视频.印象中微软有个WM控件直接可以使用,晚上研究下 2实现方式 2.1微软草根 最简单的方式,是直接使用微软的 ...

  5. Windows下一个ROracle安装与使用

    ROracle一个简短的引论: ROracle这是R连接到接入Oracle数据库DBI(Oracledatabase interface)介面.这是基于OCI一个DBI兼容Oracle司机. 具体见说 ...

  6. qml能够这么玩

    Qt 5以后qmlscene被qml所替代,/usr/bin/qml能够用来执行.qml文件.所以,我们就能够和sh一样的来写界面了. #!/usr/bin/env qml import QtQuic ...

  7. Ronco创投原则 - 硅谷创业教父Paul Graham文摘

    (天地会珠海分舵注:虽然已经尽力翻译,还是担心会和大师的原意有偏差,所以这里保留英文原文给大家作参考) Ronco创投原则 No one, VC or angel, has invested in m ...

  8. UIAutomator源码分析之启动和运行

    通过上一篇<Android4.3引入的UiAutomation新框架官方简介>我们可以看到UiAutomator其实就是使用了UiAutomation这个新框架,通过调用Accessibi ...

  9. 4行代码实现js模板引擎

    在平时编码中,经常要做拼接字符串的工作,如把json数据用HTML展示出来,以往字符串拼接与逻辑混在在一起会让代码晦涩不堪,加大了多人协作与维护的成本.而采用前端模板机制就能很好的解决这个问题. 精妙 ...

  10. Erlang常用代码段

    十六进制字符串转为二进制 hex_to_bin(Bin) -> hex2bin(Bin). hex2bin(Bin) when is_binary(Bin) -> hex2bin(bina ...