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.把对象的属性和方法结合成一个独立的单位, ...
随机推荐
- Web项目
Eclipse部署Web项目(图文讲解) 讲解是在linux下完成的,但对windows系统,操作也是一样的,不要被吓到了 1.下载Eclipse
- KMP算法之从next[]到nextVal[]
前些日子写了一篇KMP算法的博文,浅谈数据结构之KMP(串中的模式匹配算法),在这片文章中,谈到了一个模式串K值的记录数组 next[],详细可看那篇文章,其实,前面定义的next[]数组是有一定缺陷 ...
- Java有用的经验--Swing片
Java有用经验总结--Swing篇 前言 本文前言部分为我的一些感想,假设你仅仅对本文介绍的Java有用技巧感兴趣,能够跳过前言直接看正文的内容. 本文的写作动机来源于近期接给人家帮忙写的一个小程序 ...
- javascript系列之核心知识点(二)
变量对象 变量对象是一个与执行上下文相关联的容器.它是一个和上下文密切结合的特殊对象,含有定义在上下文中的变量和函数声明.注意,函数表达式(和函数声明不同的)不包含在变量对象中. 变量对象 ...
- csu 1503: 点弧之间的距离-湖南省第十届大学生计算机程序设计大赛
这是--比量p并用交点连接中心不上弧.在于:它至p距离.是不是p与端点之间的最短距离 #include<iostream> #include<map> #include< ...
- 项目开发经常使用PHP功能
日期操作 为了便于存储.比较和交付.我们通常使用strtotime()功能转换的日期UNIX时间戳.有仅用于在显示给用户时date()成经常使用的时间格式. strtotime() 函数将不论什么英 ...
- centos7的安装
初装centos7还是在九月份,那时候关于win7 下centos7硬盘安装的资料很少,现在就好多, 在这里备份下东西吧 首先是安装的时候,关于找从那个地方找image的问题. hda ,sda分别表 ...
- Codeforces 437D The Child and Zoo(贪心+并查集)
题目链接:Codeforces 437D The Child and Zoo 题目大意:小孩子去參观动物园,动物园分非常多个区,每一个区有若干种动物,拥有的动物种数作为该区的权值.然后有m条路,每条路 ...
- 【翻译自mos文章】SYS_OP_C2C 导致的全表扫描(fts)/全索引扫描
SYS_OP_C2C 导致的全表扫描(fts)/全索引扫描 參考原文: SYS_OP_C2C Causing Full Table/Index Scans (Doc ID 732666.1) 适用于: ...
- 分享一个SQLSERVER脚本
原文:分享一个SQLSERVER脚本 分享一个SQLSERVER脚本 很多时候我们都需要计算数据库中各个表的数据量很每行记录所占用空间 这里共享一个脚本 CREATE TABLE #tablespac ...