返回介绍

2.3 选择器,动态链接,多态

发布于 2025-02-24 22:44:36 字数 3894 浏览 0 评论 0 收藏 0

谁来邮递消息呢? 构造器被 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文