C++内存中的封装、继承、多态(上)
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++内存中的封装、继承、多态(上)的更多相关文章
- C++内存中的封装、继承、多态(下)
上篇讲述了内存中的封装模型,下篇我们讲述一下继承和多态. 二.继承与多态情况下的内存布局 由于继承下的内存布局以及构造过程很多书籍都讲得比较详细,所以这里不细讲.重点讲多态. 继承有以下这几种情况: ...
- java面向对象(封装-继承-多态)
框架图 理解面向对象 面向对象是相对面向过程而言 面向对象和面向过程都是一种思想 面向过程强调的是功能行为 面向对象将功能封装进对象,强调具备了功能的对象. 面向对象是基于面向过程的. 面向对象的特点 ...
- 浅谈学习C++时用到的【封装继承多态】三个概念
封装继承多态这三个概念不是C++特有的,而是所有OOP具有的特性. 由于C++语言支持这三个特性,所以学习C++时不可避免的要理解这些概念. 而在大部分C++教材中这些概念是作为铺垫,接下来就花大部分 ...
- Java三大特性(封装,继承,多态)
Java中有三大特性,分别是封装继承多态,其理念十分抽象,并且是层层深入式的. 一.封装 概念:封装,即隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别:将抽象得到的数据 ...
- Java基础——面向对象(封装——继承——多态 )
对象 对象: 是类的实例(实现世界中 真 实存在的一切事物 可以称为对象) 类: 类是对象的抽象描述 步骤: 1.定义一个类 (用于 描述人:) ( * 人:有特征和行为) 2.根据类 创建对象 -- ...
- Python 入门 之 面向对象的三大特性(封装 / 继承 / 多态)
Python 入门 之 面向对象的三大特性(封装 / 继承 / 多态) 1.面向对象的三大特性: (1)继承 继承是一种创建新类的方式,在Python中,新建的类可以继承一个或多个父类,父类又可以 ...
- php面向对象 封装继承多态 接口、重载、抽象类、最终类总结
1.面向对象 封装继承多态 接口.重载.抽象类.最终类 面向对象 封装继承多态 首先,在解释面向对象之前先解释下什么是面向对象? [面向对象]1.什么是类? 具有相同属性(特征)和方法(行为)的一 ...
- OOP三大核心封装继承多态
OOP支柱 3 个核心:封装 继承 多态 封装就是将实现细节隐藏起来,也起到了数据保护的作用. 继承就是基于已有类来创建新类可以继承基类的核心功能. 在继承中 另外一种代码重用是:包含/委托,这种重用 ...
- python面向对象(封装,继承,多态)
python面向对象(封装,继承,多态) 学习完本篇,你将会深入掌握 如何封装一个优雅的借口 python是如何实现继承 python的多态 封装 含义: 1.把对象的属性和方法结合成一个独立的单位, ...
随机推荐
- 【转】细说Cookie
阅读目录 开始 Cookie 概述 Cookie的写.读过程 使用Cookie保存复杂对象 Js中读写Cookie Cookie在Session中的应用 Cookie在身份验证中的应用 Cookie的 ...
- Swift得知——使用和分类功能(四)
Swift得知--使用和分类功能(四) 总结Swift该功能使用的总可分为七类 1 ---- 没有返回值,没有參数的函数 2 ---- 有參数和返回值的函数 3 ---- 使用元祖来返回多个值 4 - ...
- ASP.NET 5 (vNext)
ASP.NET 5 (vNext) 理解和入门 概述 ASP.NET 5 (又称为vNext) 是自ASP.NET产生15年以来一次革命性的更新, 我们可以从以下几点来理解其概貌和意义: ASP. ...
- AngularJS 疑难问题解决汇总
AngularJS 防止页面闪烁的方法 angularjs filter 详解 学习资料1 学习资料2 在 AngularJS 应用中处理单选框和复选框 学习资料3 AngularJS 之 Facto ...
- ThinkPHP神秘应用架构扩展
ThinkPHP应用模式提供了机会,改变核心框架.它可以让你的应用程序,以适应环境和其他许多不同的需求. 每一个应用模式都有自己的模式定义文件,相对与ThinkPHP3.1版本号.ThinkPHP3. ...
- 从[java.lang.OutOfMemoryError: Java heap space]恢复
出现java.lang.OutOfMemoryError: Java heap space该错误或者是程序问题,或者被分配到JVM内存真的是不够的. 一般来说都是能够事前可控解决的. 可是假设不可控的 ...
- ionic入门之基本布局
目录: 简介 Hybrid vs. Others ionic CSS框架 基本布局 布局模式 定高条块:.bar .bar : 位置 .bar : 嵌入子元素 .bar : 嵌入input 内容:.c ...
- MySQL 存储过程 经常使用语法
MySQL 存储过程是从 MySQL 5.0 開始添加的新功能.存储过程的长处有一箩筐.只是最基本的还是运行效率和SQL 代码封装.特别是 SQL 代码封装功能,假设没有存储过程,在外部程序訪问数据库 ...
- 从源代码上分析ListView的addHeaderView和setAdapter的调用顺序
ListView想要加入headerview的话,就要通过addHeaderView这种方法,然后想要为ListView设置数据的话,就要调用setAdapter方法了.可是,在调用addHeader ...
- javascript日历插件
原文:javascript日历插件 javascript日历插件 最近在尝试着写javascript日历插件,所以也到github上看国外人日历源码,或者国内人写的好点的,也在研究点,虽然看到网上有一 ...