【问题标题】:How can I create tree in C that has different types of values in nodes?如何在 C 中创建在节点中具有不同类型值的树?
【发布时间】:2017-01-24 11:54:42
【问题描述】:

我必须制作一棵树,它在除叶子之外的所有节点中都有一种类型的值,我不知道该怎么做,甚至可能。例如。所有节点中的 int,但叶子中的 char。我是编程新手,非常感谢您的帮助。谢谢

【问题讨论】:

  • googleunion 关键字。然后谷歌标记了工会。
  • 我不会那样做的。你为什么要这样做? charint 更小int,所以让它们都为int 是可以的。如果您坚持,请使用union {int i; char c;} data;。如果你的意思是一个 string 那么这样做可能更合乎逻辑,但仍然不是你真正必须做的事情。因为您知道哪些节点属于哪些类型,所以您似乎真的根本不需要union 或任何通用数据类型。
  • @IharobAlAsimi 我只是举了一个例子,它比 int 和 char 更复杂,我正在为索引顺序数据组织创建索引树,我有两种结构,一种用于叶子,一种用于其余因为叶子包含指向另一个结构列表的指针...
  • 请看reference(特别是在Union-like classes部分有一个很好的例子)和这个wiki page(还有一个例子)
  • 在这种情况下使用void * 指针并转换为正确的类型。但我仍然怀疑您使用了错误的技术(可能是从另一种编程语言借来的)。泛型类型不是 c 的用途,虽然你可以做到,但你也需要有很多经验。

标签: c tree nodes


【解决方案1】:

在您的情况下,知道节点存储的内容(节点中的整数和叶子中的字符),检查节点是否为叶子就足够了,即左右是否为空。然后使用联合值:

struct MYTREE {
    struct MYTREE *left, *right;
    union {
        char *charval;
        int intval;
    } value;
};

【讨论】:

  • 在 C 中创建“变体”几乎总是错误的解决方案。应该避免将相同的内存用于不相关的目的。
  • @Lundin,嗯??这就是工会的用途。请注意,这里存储的信息(正在使用的成员)在数据结构之外,否则需要一个鉴别器(在联合之外)来知道联合的哪个成员正在使用。
  • 不,唯一合理的联合使用是类型双关语。也就是说,通过各种不同的类型访问完全相同的二进制数据,而不是将几种不相关的二进制数据存储在同一个地方。参考例如 MISRA-C:2012 规则 19.2。
  • @Lundin,“不相关”的方面不是你要评论的。看看yacc的yystype
  • @Lundin,您的参考资料谈到“应该”并且没有给出任何理由。我不认为 MISRA 是公认的标准制定方。
【解决方案2】:

我部分同意 Lundin 和 Paul 的观点。我会将数据保持为 void*,并通过检查左右是否为 NULL 来检查节点是否为叶子。这是一个简单的开始:

#include <string.h>

typedef struct node node;
struct node {
    node* right;
    node* left;
    void* data;
};

int isLeaf(node* n)
{
    return n->right == NULL && n->left == NULL;
}

/* you can also make the two functions below a macro, but that's off-topic */
void addInt(node* n, int i)
{
    n->data = malloc(sizeof(int));
    memcpy(n->data, &i, sizeof(int));
}

void addChar(node* n, char i)
{
    n->data = malloc(sizeof(char));
    memcpy(n->data, &i, sizeof(char));
}

node* node_new()
{
    node* root = malloc(sizeof(node));
    root->right = NULL;
    root->right = NULL;
}

int main()
{
    node* root = node_new();
    addInt(root, 3);

    /* assume you have some node* n1 */
    if (n1->isLeaf())
      addChar(n1, 't');
}

【讨论】:

    【解决方案3】:

    存储泛型类型的规范方法是这样的:

    typedef enum
    {
      TYPE_INT,
      TYPE_STR,
    } type_info_t;
    
    typedef struct node_t
    {
      struct node_t* left;
      struct node_t* right;
      void*          data;  
      type_info_t    type;
    } node_t;
    
    
    ...
    
    int some_value = 1;
    node_t node1 = {NULL, NULL, &some_value, TYPE_INT};
    node_t node2 = {NULL, NULL, "hello",     TYPE_STR};
    

    您应该避免使用联合创建“变体”,这样做是不好的做法:

    • 变体在维护期间根本无法很好地扩展。如果您需要添加比现有更大的类型,数据将相应增长。变体永远不能小于其最大成员 + 填充字节。

    • 它们效率低下,分配的内存比需要的多,包括最坏情况下的填充。

    • 如果变体中的不同类型具有不同的大小,则它们不一定具有相同的对齐要求。变体中的对齐方式未指定。同样,字节序可能是一个问题。

    • 使用变体的代码可能难以阅读和理解。可能会犯错误,例如先写入小类型,然后读取较大类型,从而导致较大类型部分包含不确定数据。

    • 您只能显式初始化变体中的一个成员。这意味着如果变量是通过设置成员 x 来初始化的,然后通过访问成员 y 来使用,那么成员 y 可能包含一个不确定的值。

    (以上内容涵盖了 MISRA-C:2012 规则 19.2 的大部分基本原理)

    【讨论】:

    • Lundin,你的大部分论点我觉得有点过分或不真实。它们完全根据使用的类型进行缩放(如果它们都是指针,它们的大小都是相同的,但是编译器现在提供了类型检查),所以完全没有效率低下; “您只能初始化 1 个成员”,确实如此 - 如果您有其他要求,请使用结构。等等。
    • 在 C 的 -fno-strict-aliasing 方言中,或任何将公共初始序列应用于作为公共联合 type 成员的所有结构实例的方言中,没有需要变体来浪费空间。在 gcc 的默认方言中,使用变体的代码必须写得笨拙,并且可能会浪费一些空间,尽管不像上面显示的那样单独分配变体部分通常会浪费那么多。
    • 这不是我自己编造的,这是程序员的常识。如果您将 1 uint8_t 与占用 1MB 空间的大型结构一起存储,则无论您是否实际使用该结构成员,该联合都将占用 1MB 空间。这是明显的空间浪费,不管它扩展性差的原因。使用指针,您只需分配实际使用的内存。此外,像我的示例中这样的通用结构使您还可以编写通用代码,这使程序更易于维护并且可能更快。
    • @supercat 这仅适用于具有公共初始序列的聚合,这更像是一种特殊情况。这里关心的不是指针别名和类型双关——这正是联合存在的原因。
    • @Lundin:如果有一个静态或自动持续时间的联合,如果它保存大小不同的东西,它将浪费空间,但树节点通常保存在分配的存储中,并且分配可以适当地调整大小. CIS 在将其应用于出现在共同联合 type 中的对象的编译器上很有用,因为它允许具有以相同信息开始的节点,如链接节点、节点类型、节点大小等. 后面是特定于每种特定类型节点的信息。
    猜你喜欢
    • 1970-01-01
    • 2010-12-09
    • 2012-11-06
    • 2013-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多