C 多个结构,在一个方法中需要访问的相同字段

C 多个结构,在一个方法中需要访问的相同字段,c,struct,generic-programming,C,Struct,Generic Programming,我目前正在尝试用C编写一些lil文字控制台游戏 为此,我需要能够打印窗口一样的结构在。。。好C 我想使用一种通用的渲染方法,让我们称之为frame_render。。。呈现所有不同的ui元素 现在的问题是:如何解决这个问题 在这种情况下: // Theoretical base struct frame { int x; int y; int width; int height; } struct button { int x; int y; int width; int height; ...

我目前正在尝试用C编写一些lil文字控制台游戏

为此,我需要能够打印窗口一样的结构在。。。好C

我想使用一种通用的渲染方法,让我们称之为frame_render。。。呈现所有不同的ui元素

现在的问题是:如何解决这个问题

在这种情况下:

// Theoretical base
struct frame { int x; int y; int width; int height; }
struct button { int x; int y; int width; int height; ... }
struct whatever { int x; int y; int width; int height; ... }
如何确保我的x、y、宽度和高度始终处于正确的位置记忆? 一开始就把它们按同样的顺序排列就足够了吗


另外,如何设计方法头来接受它?

经典方法是有一个包含所有可能对象的并集的结构和一个枚举,该枚举标识传递的对象:

struct renderable {
    enum {FRAME, BUTTON, WHATVERE, ...} type;
    union {
        struct frame frame;
        struct button button;
        struct whatever whatever;
        ....
    } data;
};
将此结构传递给渲染器后,使用类型字段上的开关提取坐标,等等:

void renderer (renderable *obj, ...) {
    ...
    switch(obj->type) {
        case FRAME: x = obj->data.frame.x; ...; break;
        case BUTTON: x = obj->data.button.x; ...; break;
        ...
    }
    ...
}
据说这种怪癖促使Stroustrup发明C++:

编辑

另一个经典的解决方案是拥有一个单独的结构,该结构具有任何对象的尺寸和位置:

struct geometry {
    int x, y, width, height;
}
您可以将此结构存储在任何特定于对象的结构的开头,并使用强制转换来访问它:

struct frame {
    struct geometry geo;
    // more stuff
};

struct frame frame = {....};
rendered((void*)&frame, ...);
在渲染器中:

void renderer (void *obj, ...) {
    ...
    struct geometry *geo = (struct geometry *)obj;
    geo->x ...
}
后一种方法可能有些不安全。要使其100%安全,请将几何体与特定于对象的信息分离,并将它们作为两个单独的结构传递给渲染器

一开始就把它们按同样的顺序排列就足够了吗

是的,如果你像上面那样小心的话

另外,如何设计方法头来接受它

有不同的方法可以做到这一点

这里有一个例子,使用[C丑恶]等价的C++基类:

enum type {
    FRAME,
    BUTTON,
    WHATEVER
};

struct geo {
    int x;
    int y;
    int width;
    int height;
    enum type type;
};

struct frame {
    struct geo geo;
};

struct button {
    struct geo geo;
    int updown;
};

struct whatever {
    struct geo geo;
    int what;
    int ever;
};

void
frame_render(struct geo *geo)
{
    struct frame *frm;
    struct button *but;
    struct whatever *what;

    switch (geo->type) {
    case FRAME:
        frm = (struct frame *) geo;
        frame_render_frame(frm);
        break;

    case BUTTON:
        but = (struct button *) geo;
        printf("x=%d y=%d updown=%d\n",geo->x,geo->y,but->updown);
        frame_render_button(but);
        break;

    case WHATEVER:
        what = (struct whatever *) geo;
        printf("x=%d y=%d what=%d ever=%d\n",
            what->geo.x,what->geo.y,what->what,what->ever);
        frame_render_whatever(what);
        break;
    }
}
下面是一种使用虚拟函数表的方法:

enum type {
    FRAME,
    BUTTON,
    WHATEVER
};

struct geo;

// virtual function table
struct virtfnc {
    void (*calc)(struct geo *);
    void (*render)(struct geo *);
};

struct geo {
    int x;
    int y;
    int width;
    int height;
    enum type type;
    struct virtfnc *fnc;
};

struct frame {
    struct geo geo;
};

struct button {
    struct geo geo;
    int updown;
};

struct whatever {
    struct geo geo;
    int what;
    int ever;
};

void
frame_render(struct geo *geo)
{
    struct frame *frm = (struct frame *) geo;

    // whatever
}

void
button_render(struct geo *geo)
{
    struct button *but = (struct button *) geo;

    // whatever
}

void
whatever_render(struct geo *geo)
{
    struct whatever *what = (struct whatever *) geo;

    // whatever
}

void
any_render(struct geo *geo)
{

    geo->fnc->render(geo);
}
这里有第三种使用联合的方法。它更简单,但要求基本结构与最大的子类一样大:

enum type {
    FRAME,
    BUTTON,
    WHATEVER
};

struct frame {
    ...
};

struct button {
    int updown;
};

struct whatever {
    int what;
    int ever;
};

struct geo {
    int x;
    int y;
    int width;
    int height;
    enum type type;
    union {
        struct frame frame;
        struct button button;
        struct whatever what;
    } data;
};

void
any_render(struct geo *geo)
{

    switch (geo->type) {
    case FRAME:
        render_frame(&geo->data.frame);
        break;

    case BUTTON:
        render_button(&geo->data.button);
        break;

    case WHATEVER:
        render_whatever(&geo->data.what);
        break;
    }
}
更新:


这种方法安全吗?例如,将所有内容放入frame*类型的数组中,然后只访问frame->geo?或者这会不会导致以后的免费电话出现问题

如果使用派生类型(如frame、button)而不是基类型geo:mallocsizeofstruct button完成分配,则free没有问题

要有一个简单的[形状]数组,需要使用union方法,即所有派生结构必须具有相同的大小。但是,如果我们有一些比其他子类型占用更多空间的子类型,这将是浪费:

struct polyline {
    int num_points;
    int x[100];
    int y[100];
};
使用间接指针数组的方法1或2[其中子类型结构的大小不同]仍然可以做到这一点:

void
all_render(struct geo **glist,int ngeo)
{

    for (;  ngeo > 0;  --ngeo, ++glist)
        any_render(*glist);
}

而不是一个不同形状的数组,我会考虑一个[双]链表。这允许子类型结构具有不同的大小。我们将在struct geo中添加struct geo*下一个元素。然后,我们可以做:

void
all_render(struct geo *geo)
{

    for (;  geo != NULL;  geo = geo->next)
        any_render(geo);
}
列表方法可能更可取,尤其是如果我们在动态基础上添加/删除形状[或根据Z深度对其重新排序]


或者,某些形状可能包含其他形状。因此,我们可以将struct geo*子对象添加到struct geo。然后,很容易绘制一个包含框,然后通过遍历子列表绘制该框内的所有形状。如果我们选择子结构,我们还可以添加struct parent*parent,这样每个形状都知道包含它的形状。

如果所有结构都以相同类型的成员开始,以相同的顺序,相应的成员将具有相同的偏移量。大多数编译器可以配置为允许使用任何结构类型的指针来检查任何其他编译器的公共初始序列的成员,但存在一些问题:

在一些不寻常的平台上,如果一个对象后面跟有填充字节,那么将该对象和填充字节一起写入的指令可能会比只写入该对象的指令更快,后者可能存储了无意义的值。如果某个成员在某些结构中后跟填充字节,但在另一个结构中后跟有意义的数据,则使用后跟填充字节的类型写入该成员可能会用无意义的值重写填充字节,从而损坏其他结构类型中下列成员的值。我不知道当前使用的任何体系结构中,这对除位字段以外的任何结构成员都是一个问题,我也不知道当前的任何实现中,这对这些成员也是一个问题,但在某些平台上可能会出现这种可能性,尤其是位字段

假设:

int readField1OfS1(struct s1 *p) { return p->field1; }

struct s2 *myStruct2Ptr;

if (readField1ofS1((struct s1*)myStruct2Ptr)
   ...
某些编译器(如gcc和clang)无法可靠地允许函数返回的值可能依赖于调用时struct s2类型对象的公共初始序列的一部分所持有的值,除非禁用优化,例如使用-fno严格别名选项。我认为函数调用e中存在从结构s2*到结构s1*的强制转换 xpression应该允许高质量的编译器认识到,任何函数可能对struct s1类型的对象执行的操作都可能在struct s2上执行,但是由于标准没有明确要求,gcc和clang的作者拒绝做出任何努力来可靠地识别这种构造,即使在上述简单的情况下也是如此


使用公共初始序列规则的代码几乎可以在任何适当配置的编译器上可靠地工作,但某些编译器(如gcc和clang)必须使用-fno严格别名选项进行特殊配置。自1974年以来,利用公共初始序列保证的能力一直是该语言的一个成熟组成部分,当编写该标准时,熟悉该语言的任何人都会理解,它的设计目的是允许类似上述的构造,编译器应该很容易识别这些构造。由于标准的作者没有明确要求以有用的方式遵守CIS保证,但是,clang和gcc的作者决定,他们宁愿声称依赖几十年的CIS保证的程序被破坏,也不愿遵守40多年的先例。

如何将结构传递给方法?通过void*?计划将它们或多或少地传递到frame_renderframe*中。他们将坐在一个列表中,通过for循环进行迭代。这种方法安全吗?例如,将所有内容放入frame*类型的数组中,然后只访问frame->geo?或者这会不会给以后的免费电话带来任何问题?`不过,我有点喜欢这种方式。。。它要求在我的可渲染或框架结构中所有类型都是已知的。如果你不知道所有类型,你将如何渲染它们?它们的渲染方式都是相同的:这才是真正有趣的部分。。不过,上面的场景只是一些细分的示例。。差异很小,主要与它们的实际使用方式有关,与渲染无关。根据supercat发布的内容,这在某些编译器(如gcc)上可能会有问题。我想我会接受克雷格·埃斯蒂的答案,只是因为时间和完整性。不过我还是要欣赏一下:经典方法实际上是使用指向任何适当结构类型的指针;一个著名的例子是SOCK_ADDR类。我在那个时代看到的代码很少使用联合来实现这个目的。不幸的是,当编译器编写者询问标准的编写者是否需要编译器维护CIS保证,即使在这样做毫无用处的情况下,标准的编写者也同意没有必要让标准授权它,考虑到那些寻求编写高质量编译器的人会在它们有用与否的情况下支持它们…@X39:我认为很明显,标准的作者希望它能够工作,而在实践中,只要您确保在需要它的编译器上使用适当的标志,它就会工作。我所知道的唯一两个需要这样一个标志来使用CIS保证的编译器是clang和gcc,但可能还有其他编译器。请注意,如果没有此标志,clang和gcc肯定不支持此类代码。@x39:请编写代码的意义,并记录使其工作所需的内容。该标准没有试图完全指定一种对任何特定用途都有用的语言,而由clang和gcc处理并启用最大优化的方言对于一些不涉及处理未知来源输入的特殊用途可能非常有用,但实际上不适用于大多数其他用途。与其弯腰用不合适的方言编写代码,不如在适当的时候使用流行的扩展名,只要记录您正在这样做。@X39:BTW,我忘了提到的一个细节:了解限制限定符,并在适当的时候使用它。正确使用该限定符可以重新启用大多数由-fno严格别名阻止的有用优化,以及其他许多优化。我觉得奇怪的是,直到restrict使类型访问规则变得基本上不必要之后,编译器才真正积极地使用类型访问规则。@X39:在某些情况下,用基类型作为前缀的方法可以工作,但根据对象的大小和对齐方式,最终可能需要许多结构包含其他不必要的填充。这种方法也可能与未来的指针出处概念相冲突,这些概念正在被纳入标准中。给定结构foo{struct bar b;int q;..}f;编译器将受益于知道是否调用doSomething&f.b;可能会影响f.q,我认为推动指针出处的人希望编译器认为不会影响f.q。@X39:如果程序员编写doSomethingstruct s1*struct2Ptr;关注这些事情的编译器将拥有识别指针接收者可能将其作为结构s1或结构s2处理所需的所有信息。如果一个程序员 写入doSomething&struct2Ptr->header;编译器将不知道doSomething是将其操作仅限于头,还是可以访问所有头。我相信应该为编译器提供安全有效地优化所需的信息,而仅仅传递头指针是不行的。