2.3 选择器,动态链接,多态
谁来邮递消息呢? 构造器被 new()
所调用,对于大多数内存区域是不被初始化的:
void * new (const void * _class, ...)
{
const struct Class * class = _class;
void * p = calloc(1, class -> size);
assert(p);
* (const struct Class **) p = class;
if (class -> ctor)
{
va_list ap;
va_start(ap, _class);
p = class -> ctor(p, & ap);
va_end(ap);
}
return p;
}
在一个对象的起始地方, struct Class
指针的存在是极其重要的。这也是我们在 new()
中初始化它的原因:
如上图右边的类型描述 class
在编译的时候已经被初始化。对象是在运行时被创建的,接下来图中的虚线关联才被插入。在语句:
* (const struct Class **) p = class;
中, p
指向对象的内存区域的起始位置。我们对 p
进行了强制类型转换, p
把对象的起始位置当成一个指针,指向 struct Class
,即把参数 class
设置为这个指针的值。
接下来,若构造器是类型描述的一部分,我们调用它,并把其返回值做为 new()
的结果,即作为一个新的对象返回。2.6 部分列出一个很聪明的构造器,由于它聪明,所以能够对它自己的内存管理作出决策。
注意啦,只有明确的可见函数如 new() 能拥有可变的参数列表。参数列表被 va_list
的变量 ap
所访问, ap
被一个宏 va_start()
初始化,这个宏在 stdarg.h
头文件。 new()
仅仅能够把整个参数列表传进构造器中;因此, .ctor
也被声明成拥有 va_list
的参数,而不是它私有的参数列表。由于我们接下来要在好多函数中共享源参数列表,因此我们只传递 ap
的地址到构造器中 - 当它返回时, ap
指向参数列表的第一个参数,而参数列表本身不会被改变。
delete()
假设每个对象,也就是说,每个非空指针,指向一个类型描述。如果类型描述的析构器存在,则调用它。这里, self
扮演前面 p
的角色。我们使用局部变量 cp
来进行强制类型转换,并从 self
中获得我们所需要的信息。
void delete (void * self)
{
const struct Class ** cp = self;
if (self && * cp && (* cp) -> dtor){
self = (* cp) -> dtor(self);
}
free(self);
}
析构器,在上述 delete()
中,也会获得一次把他的返回值传进 free()
的机会,如果构造器试着去欺骗,则析构器会有更改的机会,参看 2.6 部分。如果一个对象在调用 delete()
的时候不想被删除,则可在他的析构器中返回一个空指针。
所有其他的方法都存储在类型描述中,并以相似的方式被调用。在每个例子中,我们有一个单独的接收对象 self
且我们通过它来路由我们的方法调用。
int differ (const void * self, const void * b)
{
const struct Class * const * cp = self;
assert(self && * cp && (* cp) -> differ);
return (* cp) -> differ(self, b);
}
最关键的部分,当然是一个假设,假设我们能够找到一个类型描述指针 *self
,而这个 *self
会隐藏在任意的指针 self
下面。此时此刻,至少,我们会对空指针很警惕。在每个类型描述的起始,我们将存放一个“魔法数字”,或甚至把地址或所有已知类型的地址范围与 *self
相比较,但是,在第八章会看到,我们将做更严格的检查。
不管怎么说, differ()
列举出了函数调用技术怎么被动态链接或后期链接调用的原因:即只要我们能够在一开始拥有一个正确的类型描述指针,那么我们就可以对任意的对象使用 differ()
调用。这个函数实际上被调用的时机是尽可能的晚的 - 即仅仅在实际执行期间调用,而不是之前调用。
我们可以称 differ()
为一个选择器。它是多态功能的一个例子,也就是说,一个函数能够接受不同的参数类型,且表现不同,并且这种现象是基于他们的参数类型。一旦我们实现了更多的类时,这些类在他们的描述符中都包含 .differ
,则可称 differ()
为一个泛函数,且在这些类中能够被应用于任何对象。
我们可以把这个选择器当成方法,方法自己本身不会动态链接,但仍然能够像多态函数一样的表现,因为它能让动态的连接的函数做他们真实的事情。
多态机制实际已经嵌入到很多编程语言中,例如:如在 Pascal(一种编程语言)中, write()
函数会根据参数类型不同进行不同的处理。在 C++中,操作符 + 如果被不同的类型值如整型,指针,浮点指针调用,将产生不同的结果。这个现象被称作重载,即:参数类型和操作符名结合起来决定操作结果。相同的操作符与不同的参数类型结合将产生不同的响应。
这里并没有明显的差异。因为动态连接, differ()
的表现更像一个重载函数,而且 C 的编译器也能够使得 + 看起来像多态函数 - 至少对于内嵌的数据类型来说。然而,C 编译器能够根据对 + 操作符的不同使用而产生不同的返回类型,但是函数 differ()
依靠它的参数类型只能返回相同的类型。
很多方法在不需要动态连接的情况下能够实现多态。例如,函数 sizeOf()
返回任意类型的对象的大小。
size_t sizeOf (const void * self)
{
const struct Class * const * cp = self;
assert(self && * cp);
return (* cp) -> size;
}
所有的对象都携带它们的描述符,我们可以使用描述符来获得对象的大小。注意如下的不同之处:
void* s=new(string, "text");
assert(sizeof s!=sizeOf(s));
sizeof
是 C 语言的操作符,用于在运行时以字节的个数返回参数的大小。而 sizeOf()
是我们实现的多态函数,它的参数指向一个对象,返回在运行时对象所占用的字节大小。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论