C语言中使用联合体实现继承

7

我在某处读到关于如何在C语言中实现一些面向对象的特性,这已经被证明非常有用。具体来说,我一直在考虑继承的概念。以下是一个示例:

typedef struct Circle{
     int rad, x, y;
     //Other things...
} Circle;

typedef struct Entity{
    Circle body;
    //Entity-specific items...
} Entity;

这很简单,但它允许一些巧妙的操作。指向实体的指针也是指向圆形的指针,因为实体的第一个元素始终是一个圆形。基于这个想法,我们可以构造出以下函数:
int checkCircleCollision(Circle* one, Circle* two);

并将其称为:

Entity* myentity = createEntity(/* Things specific to my entity */);
Entity* myotherentity = createEntity(/* Different things */);
//Did they collide?
if (checkCircleCollision(myentity, myotherentity)){
    /* ... */
}

这很棒,但我遇到了一个问题。如果我也想让我的实体成为矩形怎么办?我有一个解决方案,但我想确认它是否总是有效,无论编译器如何。我的联合知识非常有限。

//Circle defined as above...
typedef struct Rectangle{
    int x, y, w, h;
    //Other things...
} Rectangle;

int checkRectangleCollision(Rectangle* one, Rectangle* two);
int checkRectangleCircleCollision(Rectangle* rect, Circle* circ);

typedef struct Entity{
     union{
         Rectangle rect;
         Circle circ;
     } shape;
     int type;
     //Entity things...
}

现在可以完全安全地假设一个实体(Entity)的第一个元素是根据其初始化是RectangleCircle中的一个吗?此外,它是否可以用于上述三个函数中的任何一个?如果有相关标准的引用,则额外加分。为了更清楚地表达,我希望这样做:

Entity* rectentity = createEntity(RECTANGLE, /* width/height/etc */);
Entity* circentity = createEntity(CIRCLE, /* rad/x/y/etc */ );
if (checkRectangleCircleCollision(rectentity, circentity)){
     /* ... */
}

如果您的类型位于结构的顶部而不是底部,调试将更容易。每当您向联合添加新结构时,类型的位置都会发生变化。如果您在没有源代码的情况下使用调试器,则将类型放在顶部会更有帮助。 - cup
1
@BrainSteel Entity是圆形和矩形的父类吗? - this
3
与其在Entity结构体中嵌入子结构体,不如相反地做。只声明一个仅包含自己成员的Entity结构体。然后想要添加Circle时,创建一个Circle结构体,在其开头声明一个内部的Entity结构体。这样你就不必处理类型问题了。每个子元素在结构体开头都有它的父元素,而不是每个父元素在union中都有可能有多个子元素。 - this
@self. +1。此外,这种方法很灵活(您不必修改父类来添加子类),并且内存效率高(Circle 占用 4 个 int 的空间,尽管它只使用了 3 个)。 - el.pescado - нет войне
@self。请纠正我如果我错了,但这不是意味着所有的“Circle”都必须是“Entity”吗?实际上,我有第二个父类可能需要是一个“Circle”。 - BrainSteel
显示剩余3条评论
2个回答

4

无论其活动字段是什么,union总是对齐在相同的内存地址上。例如,考虑以下情况:

#include <stdio.h>

int main(void)
{  
    union typeUnion
    {
        int i;
        float f; 
    } u;

    u.i = 5;    
    printf("%-4d (%p)\n", u.i, &u.i);

    u.f = 3.14;
    printf("%.2f (%p)", u.f, &u.f);

    return 0;
}

我的电脑上的输出:

5    (0x22aac0) 
3.14 (0x22aac0)

这直接并迅速地回答了我的问题。非常感谢。 - BrainSteel

4
现在我们可以完全安全地假设一个实体的第一个元素是一个矩形或圆形,具体取决于其初始化。

是的。

以下是相关标准的引用:

  • "联合类型描述了一组重叠的非空成员对象,每个对象都有一个可选指定的名称和可能不同的类型。" (C99, 6.2.5.20)

  • "指向联合对象的指针经过适当转换后,指向其每个成员(如果成员是位域,则指向其中所在的单元),反之亦然。" (6.7.2.1.14)


@BrainSteel 不用谢。这里是标准的终极来源 - user529758
指向“实体”的指针是指向结构体而不是联合体的指针。标准中有一个不同的段落来说明这一点。当一个适当转换过的结构体指针指向其第一个成员时,如果该成员是一个联合体,则新指针再次转换为指向成员类型的指针时,将指向该成员。但是,从结构体指针直接转换为结构体第一个成员的成员指针的单个转换是否完全符合C标准并不清楚。 - Eric Postpischil
1
@EricPostpischil “一个指向实体的指针是指向结构体而不是联合体的指针。标准中有一个不同的段落来说明这一点。” - 是的,如果你读了问题,你会发现OP已经意识到“指向Entity的指针也是指向Circle的指针,因为Entity的第一个元素总是Circle”。此外,我没有对所需转换次数做出任何断言。我只回答了问题。如果你是对的,那么需要两个强制转换才能使它工作,那没关系,我没有说它不是这种情况。 - user529758
@EricPostpischil确实,OP的陈述是不正确的;我应该写他知道“指向结构体对象的指针,经过适当转换,指向其初始成员[...],反之亦然”(6.7.2.1.13)这一事实。但是:“问题中的代码展示了如何进行转换”-但这是否合适?据我所知,需要进行显式转换,我打算通过包含第二个引用来展示这一点。因此,问题中旨在展示用例的代码是有错误的,不是吗? - user529758
@BrainSteel 没问题,如果你知道了,那就好办了。只需要进行强制类型转换,就可以了。 - user529758
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接