如何在C语言中检查结构体的类型

5

我在C语言中创建了一个简单的列表结构,使用void*指针来保存任何类型的数据,类似于:

struct node
{
    void *the_data;
    node *next;
};

它目前运行正常,但我有一些列表,例如一个列表仅包含struct_a,另一个列表仅包含struct_b。我需要检查列表的第一个元素是struct_a类型还是struct_b类型,在单个if()问题中,以便我知道该列表持有的数据。如何做到这一点?谢谢。
编辑:
我想出了一个简单的解决方案,现在对于这个问题已经足够了,不知道它是否最好。
struct node
{
    void *data;
    node *next;
}

我添加了一个"描述符"节点,其中包含第一个节点的地址和类型信息:

struct list_desc
{
    node *list;
    short int type;
}

我将使用它和一些类似于宏的东西

#define __data_type1 10
#define __data_type2 20

所以我之后可以进行比较,例如 if( struct.type == __data_type1 ) 等等...


2
添加一个类型字段,以便记录指针实际指向的内容?空指针就是这样...一个没有任何线索的内存地址。 - Marc B
你的意思是 the_data 可以指向不同的类型吗? - Eugene Sh.
4个回答

6

C语言不提供运行时类型信息。您需要自己添加这些信息。有两种基本方法(假设使用C99或C11):

您可以使用一个(可能带有匿名联合的)结构体:

struct Node {
    enum {
        OBJ_TYPE_A,
        OBJ_TYPE_B
    } type;
    union {
        struct NodeA a;
        struct NodeB b;
    };    // anonymous, if a name is added, you have an additional field name
};

...

struct Node *node = ...;

if ( node->type == OBJ_TYPE_A ) {

    node->a.a_field = whatever;

...

缺点是你必须在一个地方定义联合,不能在另一个模块中扩展它。


更好的方法是使用匿名结构构建真正的类层次结构:

struct Node {
    enum {
        OBJ_TYPE_A,
        OBJ_TYPE_B
    } type;
    struct Node *next;
};

struct NodeA {
    struct Node;    // base class, first field!
    int a_field;
};

struct NodeB {
    struct Node;    // base class, first field!
    float b_field;
};

void take_node(struct Node *node)
    // takes a generic pointer to the base-class
{

    if ( node->type == OBJ_TYPE_A ) {
        struct NodeA *a_node = (NodeA)node;
        a_node->a_field = whatever;
    } else if ...

    ...

}

// until here that is normal C99

int main(void)
{
    struct NodeA anode = { .type = OBJ_TYPE_A, .a_field = 42 };
    struct NodeB bnode = { .type = OBJ_TYPE_B, .b_field = 4.2 };
    take_node((Node *)&anode);    // standard: inhibits type checking
    take_node(&anode);    // plan9-extension: no cast required
    take_node(&bnode);    // plan9-extension: no cast required
}

优点是在创建新的子结构时,无需扩展基础结构(它不依赖于子结构)。匿名结构允许创建直接子代的子代并像面向对象编程中一样访问字段(无需使用一长串字段指定符:child1_1_1_ptr->child1_1.child1.base.base_field,而只需使用child1_1_1_ptr->base_field)。

take_node 的后两个调用使用了 gcc C 扩展选项 -fplan9-extensions。这避免了向基类强制转换,同时仍然可以进行类型检查(基类或子类)。这已经是向面向对象编程迈出的重要一步了。

这可以扩展为全尺寸的面向对象编程方法,包括虚方法。然而,使用真正的面向对象编程语言如 C++ 更加简单。


3

C语言中不包含任何关于指针指向的运行时信息。你可以创建一个包含类型字段的结构体和一个包含其他你想要存储的内容的联合体。

enum node_type {
    NODE_TYPE_STRING,
    NODE_TYPE_INT,
    NODE_TYPE_DOUBLE,
    NODE_TYPE_OTHER_STRUCT
};

struct other_struct {
    // define some fields
};
struct node_data {
   enum node_type type;
   union {
       char *string;
       int i;
       double d;
       struct other_struct other;
    } data;
};

1
注意:使用内置名称的大写版本不是好的编程风格。此外,由于C语言没有用户命名空间,常量应该以一个通用名称为前缀,例如这里的NODE_TYPE_ - too honest for this site

2

C是一种编译语言。任何结构或其他变量的“类型”信息只存在于编译时,而不是运行时。如果需要多态性,您必须自己实现。

常见的方法是将一组结构的第一个元素作为标识符。可以是数字(可能是类型信息表中的索引)或类似于类型信息结构的地址。然后,您可以将指针分配给这些结构中的任何一个,并检查该第一个字段以决定如何处理它。


1
你说得没错,但原因不完全正确。C++是一种编译语言,可以支持运行时类型信息。例如,Google找到了https://en.wikibooks.org/wiki/C%2B%2B_Programming/RTTI。而C语言则不支持,它是一种简单的语言,被设计为可移植的汇编语言。在C语言中,需要额外的隐藏数据或指令的东西并不常见。 - Peter Cordes
1
是的,Go语言也包含了一些运行时类型信息。这并不完全符合我对于“完全编译”语言的理解,但我已经老了。 - Lee Daniel Crocker

1

你对此有何看法:

typedef enum tag_ElementType
{
    ElementA,
    ElementB
} en_ElementType;

typedef struct tag_Container
{
    en_ElementType ElementType;
    void *pData;
} st_Container;

在这种情况下,您的结构将如下所示:


typedef struct tag_node
{
    st_Container the_data;
    struct tag_node *next;
} st_node;

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