如何在非 OO 语言中进行继承?

Posted

技术标签:

【中文标题】如何在非 OO 语言中进行继承?【英文标题】:How do you do inheritance in a non-OO language? 【发布时间】:2009-10-29 04:26:26 【问题描述】:

我读到早期的 C++“编译器”实际上将 C++ 代码翻译成 C,并在后端使用了 C 编译器,这让我感到奇怪。我有足够的技术知识来理解它的大部分工作原理,但我不知道如何在没有语言支持的情况下进行类继承。

具体来说,你如何定义一个有几个字段的类,然后是一堆从它继承的子类,每个子类都添加自己的新字段,并且能够将它们作为函数参数互换地传递?尤其是当 C++ 允许你在堆栈上分配对象时,你怎么能做到这一点,所以你甚至可能没有指针可以隐藏在后面?

注意:我得到的前几个答案是关于多态性的。我对多态性和虚方法了如指掌。我什至做过一次关于 Delphi 中虚拟方法表如何工作的低级细节的会议演示。我想知道的是类继承和字段,而不是多态性。

【问题讨论】:

(几乎)完全重复:***.com/questions/415452/object-orientation-in-c***.com/questions/351733/…***.com/questions/1201521/… 可能还有更多 我有点困惑...字段很简单(至少对于 C 语言来说)——只需将基本结构作为派生结构中的第一件事。还是我错过了问题中的另一个细微差别? 有趣的是,当 C 不支持继承和其他面向对象特性时,人们往往很难理解(有时甚至是)如何在 C 中实现继承和其他 OO 特性,而根本不质疑如何在您甚至没有结构的汇编或机器代码中实现相同的功能。 这并不难。我知道结构在装配级别是如何工作的。 :P 为了它的价值,Comeau C++ 不还编译成 C 吗?您可能会看到它是如何完成的。 【参考方案1】:

无论如何,在 C 语言中,您可以按照 cfront 在 C++ 早期将 C++ 代码转换为 C 时使用的方式进行操作。但是您需要非常自律并手动完成所有繁重的工作。

您的“类”必须使用执行构造函数工作的函数进行初始化。这将包括初始化指向虚函数的多态函数指针表的指针。必须通过 vtbl 函数指针进行虚函数调用(它将指向函数指针的结构 - 每个虚函数一个)。

每个派生类的虚函数结构都需要是基类虚函数结构的超集。

其中的一些机制可能会使用宏隐藏/辅助。

Miro Samek 的第一版"Practical Statecharts in C/C++" 有一个附录 A - “C+ - C 中的面向对象编程”,其中包含此类宏。看起来这是从第二版中删除的。可能是因为麻烦多于其价值。如果您想这样做,只需使用 C++...

您还应该阅读Lippman's "Inside the C++ Object Model",其中详细介绍了 C++ 如何在幕后工作,通常带有关于 C 中可能如何工作的 sn-ps。


我想我明白你在追求什么。也许吧。

这样的事情怎么会起作用:

typedef 
struct foo 
    int a;
 foo;

void doSomething( foo f);   // note: f is passed by value

typedef
struct bar 
    foo base;
    int b;
 bar;


int main() 
    bar b =   1 , 2;

    doSomething( b);    // how can the compiler know to 'slice' b
                        //  down to a foo?

    return 0;

好吧,如果没有语言支持,您就无法做到这一点 - 您需要手动执行一些操作(这就是没有语言支持的意思):

    doSomething( b.base);       // this works

【讨论】:

很好的答案,但这不是我要问的。【参考方案2】:

基本上,structs-within-structs。

struct Base 
    int blah;
;

struct Derived 
    struct Base __base;
    int foo;
;

当您想将Derived * 转换为Base * 时,实际上会返回一个指向Derived 结构的__base 元素的指针,在这种情况下,它是结构中的第一件事,所以指针应该是相同的(但对于多个继承的类来说不是这样)。

如果您想在这种情况下访问blah,您可以执行derived.__base.blah 之类的操作。

虚拟函数通常使用一个特殊的函数指针表来完成,该表是每个对象的一部分,一种基本的“我的类型是什么”记录。

【讨论】:

啊哈!这就说得通了。它还解释了在处理继承层次结构时 C++ 的一些丑陋的角落。【参考方案3】:

以下是 COM 对 C 语言的处理方式。我对此有点生疏,但本质是这样的。每个“类”成员变量只是一个结构。

struct Shape

  int value;
;

struct Square

  struct Shape shape; // make sure this is on top, if not KABOOM
  int someothervalue;
;

所有的方法,实际上只是普通的功能。像这样

void Draw(Shape * shape,int x,int y)

  shape->value=10; // this should work even if you put in a square. i think...

然后,他们使用预处理器“欺骗”C 代码以显示类似的内容。

Square * square;
square->Draw(0,0); // this doesnt make sense, the preprocessor changes it to Draw(square,0,0);

唉,我不知道做了什么样的预处理器技巧来使看起来 C++ 的函数调用解析为计划中的普通 C 调用。

DirectX COM 对象以这种方式声明。

【讨论】:

【参考方案4】:

博士。 Dobb's 有一篇关于这个主题的详细文章,Single Inheritance Classes in C.

【讨论】:

【参考方案5】:

Structs-within-structs 很常见,但它使访问继承的字段变得很痛苦。您要么需要使用间接(例如child->parent.field),要么需要使用强制转换(((PARENT *) child)->field)。

我见过的替代方案更像这样:

#define COUNTRY_FIELDS \
    char *name; \
    int population;

typedef struct COUNTRY

    COUNTRY_FIELDS
 COUNTRY;

#define PRINCIPALITY_FIELDS \
    COUNTRY_FIELDS \
    char *prince;

typedef struct PRINCIPALITY

    PRINCIPALITY_FIELDS
 PRINCIPALITY;

这使类型可以直接访问继承的字段。结果对象仍然可以安全地转换为父类型,因为父字段和继承的字段从同一位置开始。

使用宏可以稍微改进语法。我在较旧的 POV-Ray 源代码中看到了这一点(但我认为它们已经转换为 C++)。

【讨论】:

【参考方案6】:

如果您想很好地了解这些东西的工作原理,请查看 glib/gdk/gtk 开源库。他们有很好的文档,整个框架都是基于 C OO 的。

【讨论】:

【参考方案7】:

您可以通过编写构造函数、setter、getter 和析构函数来模拟对象,并显式调用隐藏的 this 指针。

通过让派生对象在派生对象的结构中包含指向基对象的指针来处理继承。

【讨论】:

以上是关于如何在非 OO 语言中进行继承?的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript是如何实现继承的(六种方式)

部分元素与主要的 OO 编程概念相比如何?它们是主要的还是备用的?

面向对象的多态详解 !!

JavaScript之继承

浅谈JS的继承

OO第三单元总结