关于对象

1.1 C++对象模型

在C++中,有两种class data members:static和nonstatic,以及三种class member functions:static、nonstatic和virtual。

eg:

 

class Point {
public:
	Point(float xval);
	virtual ~Point();
	float x() const;
	static int PointCount();
protected:
	virtual ostream&
		print(ostream &os)const;
	float _x;
	static int _point_count;
};

 

简单对象模型

 

优点:C++编译器的设计复杂度较低

缺点:空间和执行期的效率

 

在这个简单模型中,一个object是一系列的slots,每一个slot指向一个members。Members按其声明顺序,各被指定一个slot。每一个data member或function member都有自己的一个slot。member本身并不放在object中,只有“指向member的指针”才放在object内。这么做可以避免“members有不同的类型,因而需要不同的存储空间”所导致的问题。

也就是说,对象只是维护了一个包含成员指针的一个表。表中存放的是成员的地址,无论是成员变量还是函数,都是同样的处理,对象并没有直接保存成员,而是保存了成员的指针。

 

表格驱动对象模型

 

为了对所有classes的所有objects都有一致的表述方式,这种对象模型是把所有与members相关的信息抽出来,放在一个data member table和一个member function table之中,class object本身则内含指向这两个表格的指针。Member function table是一系列的slots,每一个slots指出一个member function;Data member table则直接持有data本身。

C++对象模型

此C++对象模型从简单对象模型派生而来,并对内存空间和存取时间做了优化。在此模型中,Nonstatic data membebrs被配置于每一个class object之内,static data members则被存放在个别的class object 之外。Static和nonstatic fuction members也被放在个别的class object之外。Virtual functions则以两个步骤进行支持:

1.每一个class产生出一堆指向virtual functions的指针,放在表格之中。这个表格被称为virtual table。

2.每一个class object被安插一个指针,指向相关的virtual table。通常这个指针被称为vptr。vptr的设定和重置都有每一个class的constructor、destructor和copy assignment运算符自动完成。意思就是vptr不需要程序员自己设定,是在这三个函数中自动完成的。type info object的作用应该就是通过运行时类型信息程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。

 

优点:空间和存取时间的效率。

缺点:如果应用程序代码本身未曾改变,但所用到的class objects的nonstatic data members(比如下图的float _x)有所修改(增加、删除、改动),那么那些应用程序同样需要重新编译(因为nonstatic data membbers的位置是和__vptr__Point在一起的)。前述的表格驱动模型由于提供了一层间接性,所以提供了较大的弹性。但是代价是空间和执行效率两个方面。

 

加上继承

一个derived class如何在本质上模塑其babse class的实例呢?这里有一种所谓的base table模型。这里所说的base class table被产生出来时,表格中的每一个slot内含一个相关的base class地址,这很像virtual table内含每一个virtual function的地址一样。每一个class object内含一个bptr,它会被初始化,指向其base class table。

缺点:

由于间接性而导致的空间和存取时间上的额外负担

优点:

每一个class object中对于继承都有一致的表现形式:每一个class object都应该在某个固定位置上安放一个base table指针,这与base classes的大小或个数无关。

无需改变class objects本身,就可以放大、缩小,或者更改base class table。

不同的对象模型会影响程序

不同的对象模型,会导致“现有的程序代码必须修改”以及“必须加入新的程序代码”两个结果。当然,这都是由编译器完成的。

1.2关键词所带来的差异

struct关键词与class关键词的区别

1.最本质的一个区别就是默认的访问控制,默认的继承访问权限。struct是public的,class是private的。

2.在观念上,struct关键词是C的数据抽象概念,class关键词是C++的抽象数据类型观念。

策略性正确的struct

C程序员的巧妙设计可能成为C++程序员的陷阱。比如把单一元素的数组放在一个struct的尾端,于是每个struct objects可以拥有可变大小的数组

 

Struct mumble
{
	Char pc[1];
};
//读取一个字符串,为struct 本身与该字符串配置足够的内存
Struct mumble * pmumbl = (struct mumble *)  malloc(sizeof(struct mumble) + strlen(string) + 1);
Strcpy(pmumble->pc, string);

关于上述程序:

 

 

首先,结构体的末尾定义了一个char数组,只分配了1个字符。那怎么能说是可变大小数组。

往下看,他用malloc函数分配了一堆的内存。大小为结构体+字符串+1(字符串结束符)如下图所示。

 

注意:

1在sizeof struct mumble已经包含了pc[1]的内存。

2pmumb1已经分配了足够的内存,当把string对象拷贝给mumble.pc时,实现了可变大小的数组。

 

通过strcpy,将string字符串拷贝给mumble.pc,pmumble已经分配了足够的内存,因此只要赋值即可,也就达到了可变大小数组的意思了。其实想说明的是它分配的内存是在开头或末尾,这样就不会对开始放入内存中的数据有影响了。

但是如果我们改用class来声明,而该class是:

指定多个访问区段,内含数据;

从另一个class派生而来;

定义了一个或多个virtual functions。

那么或许可以顺利转化,但也许不行!这是因为C++凡处于一个access section(访问区段)的数据,必定保证其声明顺序出现在内存布局当中。然而被放置在多个access sections中的各笔数据,排列顺序就不一定了。同理,base classes和derived classes的data members的布局也未有谁先谁后的强制规定,因而也就不保证前述的C伎俩一定有效。

指针的类型

一个指向类的指针是如何与一个指向整数的指针或一个指向template Array的指针有所不同呢

ZooAnimal *px;
int *pi;
Array<String> *pta;

以内存需求的观点来说,没什么不同!它们三个都需要有足够的内存来放置一个机器地址。“指向不同类型之各指针”间的差异,既不在其指针表示法不同,也不在其内容不同,而是在其所寻址出来的object类型不同。也就是说,“指针类型”会教导编译器如何解释某个特定地址中的内存内容及其大小。

ZooAnimal *pza=&za; pa这个变量在内存中的地址是1016,它存放的内容是1000,也就是指向了地址1000,从pza的类型ZooAnimal可以看出共占用的地址空间为4+8+4=16bytes。

所以可以说,类型转换并不改变一个指针所含的真正地址,只影响“被指出内存的大小和其内容”的解释方式。

加上多态之后

class  ZooAnimal {
public:
	ZooAnimal();
	virtual ~ZooAnimal();
	virtual void rotate();
protected:
	int loc;
	string name;
};
class Bear :public ZooAnimal {
public:
	Bear();
	~Bear(); void rotate();
	virtual void dance();
protected:
	enum Dances {...};
	Dances dances_known;
	int cell_block;
};
Bear b{ "Yogi" };
Bear *pb = &b;
Bear &rb = *pb;

Bear 对象b需要24bytes(我感觉图中少了__vptr__Bear,也就是少了4个字节)。现在假设Bear object放在地址1000处,一个Bear指针和一个ZooAnimal的指针有什么不同呢?

Bear b;
ZooAnimal *pz = &b;
Bbear *pb = &b;

首先介绍一个概念

基类子对象:派生,就是对基类的某种拓展。所以派生类对象内部,一定有一份基类对象存在,派生类对象内部的这份基类对象,本质上是派生类的一个成员,称为基类子对象。

 

下面接着说明上面的指针,它们每个都指向Bear object的第一个byte。其间的差别是,pb所涵盖的地址包含整个Bear object,而pz所涵盖的地址只包含Bear object中的ZooAnimal基类子对象。
除了ZoAnimal subobject中出现的members,不能使用pz来直接处理Bear的任何members。唯一的例外是通过virtual机制。
 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值