转载请注明出处:http://blog.csdn.net/serenitymoon/article/details/7547420
1、构造与析构
让我们先实现一个简单的字符串数据类型,在后面的章节里,我们会把它放入一个集合中。 在创建一个新的字符串时,我们分配一块动态的缓冲区来保存它所包含的文本。在删除该字符串时,需要回收那块缓冲区。
new() 负责创建一个对象,而delete() 回收该对象所占用的资源。new() 知道它要创建的对象是什么类型的,因为它的第一个参数为该对象的描述符。依据该参数,我们可以用一系列if语句来分别处理每一种数据类型的对象的创建。这种做法的的缺点是,对我们所要支持的每一种数据类型,new()中都要显式地包含表示该数据类型的代码。
delete() 所要解决的问题更为棘手。它也必须随着被删除对象的类型的不同而作出不同的动作:若是一个String 对象,则必须释放它的文本缓冲区;若是在第一章中用过的那种Object 对象,则只需回收该对象自身;而若是一个Set 对象,则需要考虑它可能已经请求了很多内存块用来储存其元素。
我们可以给delete() 添加一个参数:类型描述符或者做清理工作的函数,但这种方式不仅笨拙,而且容易出错。有一种更为通用更为优雅的方式,即保证每个对象都知道如何去销毁它所占有的资源。可以让每个对象都存有一个指针域,用它可以定位到一个清理函数。我们称这种函数为该对象的析构函数。
现在new() 有一个问题。它负责创建对象并返回一个能传递给delete() 的指针,就是说,new() 必须配置每个对象中的析构函数信息。很容易想到的办法,是让指向析构函数的指针成为传递给new() 的类型描述符的一部分。到目前为止,我们需要的东西类似如下声明:
struct type {
size_t size; /* size of an object */
void (* dtor) (void *); /* destructor */
};
struct String {
char * text; /* dynamic string */
const void * destroy; /* locate destructor */
};
struct Set {
... information ...
const void * destroy;
};
看起来我们有了另一个问题:需要有人把析构函数的指针dtor 从类型描述符中拷贝到新对象的 destory 域,并且该副本在每一类对象中的位置可能还不尽相同。
初始化是new() 工作的一部分,不同的类型有不同的事情要做——new() 甚至需要为不同的类型而配备不同的参数列表:
new(Set); /* make a set */
new(String, "text"); /* make a string *
对于初始化,我们使用另一种特定于类型的函数,我们称之为构造函数。由于构造函数和析构函数都是特定于类型的,不会改变,我们把他们两个都作为类型描述的一部分传递给new() 。
要注意的是,构造函数和析构函数不负责请求和释放该对象自身所需的内存,这是new() 和delete()的工作。构造函数由new() 调用,只负责初始化new() 分配的内存区域。对于一个字符串来说,构造函数做初始化工作时确实需要申请一块内存来存放文本,但struct String 自身所占空间是由new() 分配的。这块空间最后会被 delete() 释放。而首先要做的是,delete() 调用析构函数,做与构造函数的初始化相逆的工作,然后才是delete() 回收new() 所分配的内存区域。
2、方法、消息、类与对象
delete()必须能够在不知所给对象类型的情况下定位到析构函数。因此,需要修订第2.1章节中的声明,对于所有传入delete()的对象,强调用于定位析构函数的指针必须位于这个对象的头部,而不管这些对象具体是什么类型。
这个指针又应该指向什么呢?如果我们有的只是一个对象的地址,那么这个指针可让我们访问这个对象的类型信息,诸如析构函数。这样看起来我们同样也将很快建立一个其他的类型信息函数,诸如显示对象的函数,或者比较函数differ(),又或者可以创建本对象完整拷贝的clone()函数。因此我将让这个指针指向一个函数指针表。
如此看来,我们认识到这个指针索引表必须是类型描述的一部分,并且传给new(),并且显而易见的解决方式便是把整个的类型描述作为一个对象,如下所示:
struct Class {
size_t size;
void * (* ctor) (void * self, va_list * app);
void * (* dtor) (void * self);
void * (* clone) (const void * self);
int (* differ) (const void * self, const void * b);
};
struct String {
const void * class; /* must be first */
char * text;
};
struct Set {
const void * class; /* must be first */
...
};
我们的每一个对象开始于一个指向它自身所拥有的类型描述的指针,并且通过这个类型描述,我们能定位这个对象类型描述信息:.size是通过new()分配的这个对象的长度;.ctor指针指向被new()函数调用的构造函数,这个构造函数接受被申请的区域和在初始时传递给new()的其余的参数列表;.dtor指向被delete()调用的析构函数,用来销毁接受到的对象;.clone指向一个拷贝函数,用来拷贝接受到的对象;.differ指针指向一个用来将这个对象于其他对象进行比较的函数。
大体上看看上面这个函数列表就能发现每个函数都是通过对象来选择作用于不同的对象的。只有构造函数要处理那些部分初始化的存储区域。我们称这些函数叫做这些对象的方法。调用一个方法就叫做一次消息,我们已经用self作为函数参数来标记接收该消息的对象。当然这里我们用的是纯C函数,所以self不一定得是函数的头一个参数。
一些对象将共享同样类型描述,就是说,他们需要同样数量的内存和提供同样的方法供使用。我们称所有拥有同样类型描述的对象为一个类;单独的一个对象称作为这个类的实例。到现在为止,一个类、一个抽象数据类型、一些可能的值及其的操作(就是说一个的数据类型)几乎是一样的。
一个对象是一个类的实例,也就是说,在通过new()为它分配了内存后,它就有了一个状态,并且这个状态可以通过它所属类的方法进行操作。按惯例,一个对象是一个特定数据类型的一个值。
3、选择器、动态链接与多态
待续。。。传奇私服..。