关于对象

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 obbject被安插一个指针,指向相关的virtual tabble。通常这个指针被称为vptr。

优点:空间和存取时间的效率。
缺点:如果应用程序代码本身未曾改变,但所用到的class objects的nonstatic data members有所修改(增加、删除、改动),那么那些应用程序同样需要重新编译(因为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;
以内存需求的观点来说,没什么不同!它们三个都需要有足够的内存来放置一个机器地址。“指向不同类型之各指针”间的差异,既不在其指针表示法不同,也不在其内容不同,而是在其所寻址出来的obbject类型不同。也就是说,“指针类型”会教导编译器如何解释某个特定地址中的内存内容及其大小。

加上多态之后

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机制。



猜你喜欢

转载自blog.csdn.net/wk_bjut_edu_cn/article/details/80084006