第三章、Data语意学

无虚继承的空类占一个字节,用于标记该类。有虚继承的空类至少占4个字节(可能继承的空类占很大空间)。
对齐情况
class
X{
float i;//8
char j;//1
int k;//4
double
b;//下面重新的字节8,上面用来对齐
};
sizeof(X)=24

class Y{
char j;//1
int k;//4
};
sizeof(Y)=8

class Z{
char j;//1
int k;//4
double b;
};
sizeof(Y)=16

1.Data
Member的绑定
有两种情况:
情况一:在类的函数里面返回的变量,之前是全局变量,但如今已经是类的变量。例如:
extern float
x;
class
Point3d
{
Point3d(float,float,float);
//返回的是Point3d::x,早期时时全局变量x
float
X() const{return x;}
void X(float new_x) const{x=new_x;}
//...
float
x,y,z;
};
早期的防御措施:
把所有成员声明放在类的开头。
把所有的inline函数都放在class声明之外。

情况二:成员函数的参数列表中,当他们第一次出现的时候就适当的被决议resolved完成。
typedef int length;
class
Point3d
{
//所有的length被决议为global
//_val被决议为Point3d::_val
void
mumble(length val){_val=val;}
length mumble(){return
_val;}
//...
typedef float length;
float _val;
};

2.Data Member的布局
可以用下面模板函数测试布局的先后
template<typename
class_type,typename data_type1,typename data_type2>
char*
access_order(data_type1 class_type::*mem1,data_type2
class_type::*mem2)
{
   
assert(mem1!=mem2);
    return mem1<mem2?"member 1 occurs
first":"member 2 occurs first";
}
class
X{
public:
    double j;
    int
k;
    double
b;
};
access_order(&X::b,&X::j);//测试出错,额。。

3.成员变量的存取
对于例子:
Point3d origin,*pt=&origin;
//下面两种有区别么?根据class
Point3d和data member
x的角度来说,有区别。
origin.x=0.0
pt->x=0.0;
其实只有在当Point3d是一个derived
class,而在它的继承结构中有一个virtual base class,并且存取的member(如本例x)
是一个从该virtual base
class继承而来的member时,就会有很大区别。此时对于pt,只能在运行时候才决定,经有一个而外的间接导引。
对于origin,可以在编译时期就固定了。

情况一:x是静态成员变量
它被提出在class之外,并视为global变量。每个member的存取都不会导致任何空间或者执行时间的额外负担,无论是在个别的class
objects或者在static data
member本身。
上面两个都会转为:
Point3d::x=0.0;
即使对于函数返回值
foobar().x=24.0;
都会转为:
(void)foobar();
Point3d.x=24.0;
当对它取地址时,&Point3d::x;就会获得内存地址:const
int*
如果两个类都声明同样的static member,那么它们会被改名字

情况二:非静态成员变量
对其取地址:&origin::x;等于&origin+(&Point3d::x-1);
书上说是为了区别“一个指向data
member的指针,用以指向class的第一个member”和“一个指向data member的指针,没有指向class的第一个member”

4.继承与数据成员
只继承而不要多态时,派生类会根据父类的成员变量的最大长度来alignment。在继承链上,注意不能把数据放在用于alignment的padding区域中,因为
class
concrete1{
public:
int val;
char bit1;
}
class concrete2:public
concrete1{
char bit2;
}
如果把bit2放在bit1的padding域里面,如果concrete1
*p1;concrete2 *p2=*p1;时,bit2被乱覆盖掉了。

加上多态时,数据成员可能在虚指针的上面也可能在虚指针的下面。

多重继承,要适当地调整位置才可以赋值。如
class
Point2d{
public:
//..拥有virtual接口,所以Point2d对象之中会有vptr
protected:
float
_x,_y;
};
class Point3d:public
Point2d{
public:
//..拥有virtual接口,所以Point2d对象之中会有vptr
protected:
float
_x;
};
class
Vertex{
public:
//..拥有virtual接口,所以Point2d对象之中会有vptr
protected:
Vertex
*next;
};
class Vertex3d:public Point3d,public
Vertex{
public:
//..拥有virtual接口,所以Point2d对象之中会有vptr
protected:
float
mumble;
};
Vertex3d v3d;Vertex *pv;Point2d *p2d;Point3d
*p3d;
对于下面的赋值
pv=&v3d;//考虑到pv3d可能为0,加上偏移,需要转换为pv=pv3d?Vertex
*)(((char*)&v3d)+sizeof(Point3d)):0;
p2d=&v3d;p3d=&v3d;//直接拷贝地址就可以了。

虚拟继承有两种实现:1、每个派生类多加一个指针指向一个虚拟基类表2、在virtual中放置virtual base
class的offset,正值则索引到函数,负值则索引到基类。

时间: 2024-10-03 17:27:29

第三章、Data语意学的相关文章

深度探索C++对象模型 第三章 Data 语意学

一个有趣的问题:下列 类 sizeof大小 class X{}    //1 class Y:public virtual X{} //4 or 8 class Z:public virtual X{} // 4 or 8 class A:public Y,public Z{} // 8 or 12 主要原因:为了保持每一个类生成对象在内存中的唯一性,编译器必须要给空类生成一个char来维持object的唯一性: 而virtual继承中,仅保持了base class的指针,有些编译器会继承bas

Data语意学里的一些问题

今天和小伙伴讨论了第三章data语意学的指一些的知识,感觉很有必要总结一下,似乎不总结知识就会溜走,所以冒夜写一下吧. 首先看这样的一个继承的例子: class X{ }; class Y: public virtualX { }; class Z: public virtualX { }; class A: public Y,publicZ { }; 分别对X Y Z A取sizeof,结果可能是1, 8, 8, 12.当然我的机器是1, 4, 4, 8.一般来说老的机器会是第一种结果,因为编

C++对象模型——Data 语意学(第三章)

第3章    Data 语意学 计算如下代码的sizeof结果: class X{}; class Y : public virtual X{}; class Z : public virtual X{}; class A : public Y, public Z{}; 上述X,Y,Z,A中没有任何一个 class 内含明显的数据,只表示了继承关系,所以认为每一个 class 的大小都是0.这样想法是错误的.即使 class X的大小也不为0. 一个空的 class 如: // sizeof(X

深度探索C++对象模型 第二章构造函数语意学

在使用C++时,常常会好奇或者抱怨,编译器为我们做了什么事呢? 为什么构造函数没有为我初始化呢?为什么我还要写默认构造函数呢? 2.1 Default Constructor 的构造操作 如果没有声明默认构造函数,编译器会在需要的时候帮我们产生出来. 为了避免在多个地方被需要导致重复,则编译器将产生的构造函数声明为inline方式. class Foo {public:Foo(), Foo(int) }; class Bar {public: Foo foo;char *str;} Bar ba

第三章 Linux操作系统的安装

第三章 Linux操作系统的安装 因为笔者一直都是使用CentOS,所以这次安装系统也是基于CentOS的安装.把光盘插入光驱,设置bios光驱启动.进入光盘的欢迎界面. 其中有两个选项,可以直接按回车,也可以在当前界面下输入 linux text 按回车.前者是图形下安装,可以动鼠标的,后者是纯文字形式的.建议初学者用前者安装.直接回车后,出现一下界面: 这一步是要提示你是否要校验光盘,目的是看看光盘中的安装包是否完整或者是否被人改动过,一般情况下,如果是正规的光盘不需要做这一步操作,因为太费

《操作系统精髓与设计原理》习题第三章

第三章习题 3.10.1关键术语 阻塞态:进程在某些事件发生之前不能执行,等待这种事件发生的状态. 退出态:操作系统从可执行进程组中释放出的进程,自身停止了,或者因某种原因被取消. 内核态:某些指令只能在特权状态下执行,而这种特权状态称为内核态. 子进程:由一个进程创建的进程,该进程的终止受父进程的影响. 中断:由外部事件引发进程挂起,CPU转而去处理发起中断的事件,并处理结束后恢复进程的执行. 模式切换:CPU由用户态和核心态之间相互切换. 新建态:进程创建时仅仅创建了对应的进程控制块而没有在

【深度探索C++对象模型】第二章 构造函数语意学(上)

第二章 构造函数语意学(The Semantics of Constructors) -- 本书作者:Stanley B.Lippman 一.前言 首先让我们来梳理一个概念: 默认构造函数(Default Constructor) : 是在没有显示提供初始化式时调用的构造函数.它由不带任何参数的构造函数,或是为所有形参提供默认实参的构造函数定义.如果定义的某个类的成员变量没有提供显示的初始化式时,就会调用默认构造函数(Default Contructor). 如果用户的类里面,没有显示的定义任何

HBase in Action前三章笔记

近期接触HBase,看了HBase In Action的英文版.開始认为还行,做了些笔记.可是兴许看下去,越来越感觉到实战这本书比較偏使用上的细节,对于HBase的具体设计涉及得很少.把前三章的一些笔记帖一下.后面几章内容不打算整理了.并非说书内容不好. key-value存储.强一致性,多个RegionServer节点对client端是不暴露细节的 使用场景:典型的web-search, capture incremental data, ad. click stream, content s

KnockoutJS 3.X API 第三章 计算监控属性(5) 参考手册

计算监控属性构造参考 计算监控属性可使用以下形式进行构造: ko.computed( evaluator [, targetObject, options] ) - 这种形式是创建一个计算监控属性最常见的情况. evaluator - 用于返回计算值的函数. targetObject-如果给出定义的值this时KO调用回调函数.参见部分第三章 计算监控属性(1) 使用计算监控属性以获取更多信息. options - 计算监控属性的其他属性的对象.请参见下面的完整列表. ko.computed(

第三章 rsync通过远程ssh实现数据备份

第三章 rsync通过远程ssh实现数据备份 通过远程shell访问,实现数据同步备份 拉--即将远程服务器上的数据同步到本地服务器上. 推--即将本地服务器上的数据同步到远程服务器上. 语法格式: 拉操作==>     rsync   选项    用户名@备份源服务器IP地址:备份源目录    目标目录 推操作==>     rsync   选项    备份源目录    用户名@目标服务器IP地址:目标目录 环境: 本地服务器为:DataServer.  IP地址为:192.168.88.8