我有几个头文件,归结为:

tree.h中:

#include "element.h"

typedef struct tree_
{
    struct *tree_ first_child;
    struct *tree_ next_sibling;
    int tag;
    element *obj;
    ....
} tree;

和element.h:

#include "tree.h"

typedef struct element_
{
    tree *tree_parent;
    char *name;
    ...
} element;

问题是它们都互相引用,因此树需要包含元素,元素需要包含树。

这不起作用,因为要定义'tree'结构,元素结构必须已知,但要定义元素结构,必须知道树结构。

如何解决这些类型的循环(我认为这可能与'前向声明'有关?)?

有帮助吗?

解决方案

我认为这里的问题不是缺少的包括守卫,而是这两个结构在他们的定义中需要彼此的事实。所以它是一种定义hann和egg问题的类型。

在C或C ++中解决这些问题的方法是对类型进行前向声明。如果告诉编译器元素是某种结构,编译器就能生成指向它的指针。

E.g。

在tree.h中:

// tell the compiler that element is a structure typedef:
typedef struct element_ element;

typedef struct tree_ tree;
struct tree_
{
    tree *first_child;
    tree *next_sibling;
    int tag;

    // now you can declare pointers to the structure.
    element *obj;
};

这样你就不必再在tree.h中包含element.h了。

你也应该在你的头文件周围添加包含保护。

其他提示

这里的关键观察是元素不需要知道树的结构,因为它只保存指向它的指针。树也一样。所有人都需要知道的是,存在一个具有相关名称的类型,而不是其中的内容。

所以在tree.h中,而不是:

#include "element.h"

做的:

typedef struct element_ element;

此“声明”类型“元素”和“struct element_” (说它们存在),但没有“定义”他们(说出他们是什么)。所有你需要存储一个指向blah的指针是声明blah,而不是它被定义。只有当你想要它(例如阅读成员)时,你才需要定义。您的“.c”中的代码文件需要这样做,但在这种情况下,你的标题不会。

有些人创建了一个单独的头文件,它在头部集群中正向声明所有类型,然后每个头包含它,而不是确定它真正需要哪些类型。这既不重要也不完全愚蠢。

关于包含警卫的答案是错误的 - 一般来说,这是一个好主意,你应该阅读它们并让自己一些,但它们并不能解决你的问题。

正确的答案是使用包含警戒,并使用前向声明。

包括警卫

/* begin foo.h */
#ifndef _FOO_H
#define _FOO_H

// Your code here

#endif
/* end foo.h */

Visual C ++也支持#pragma一次。它是一种非标准的预处理程序指令。作为编译器可移植性的交换,您可以减少预处理器名称冲突的可能性并提高可读性。

前向声明

转发声明你的结构。如果未明确需要结构或类的成员,则可以在头文件的开头声明它们的存在。

struct tree;    /* element.h */
struct element; /* tree.h    */

了解转发声明


// tree.h:
#ifndef TREE_H
#define TREE_H
struct element;
struct tree
{
    struct element *obj;
    ....
};

#endif

// element.h:
#ifndef ELEMENT_H
#define ELEMENT_H
struct tree;
struct element
{
    struct tree *tree_parent;
    ...
};
#endif

包含防护是有用的,但不解决海报的问题,即两个数据结构的递归依赖。

这里的解决方案是将树和/或元素声明为头文件中结构的指针,因此您不需要包含.h

类似的东西:

struct element_;
typedef struct element_ element;

在tree.h的顶部应该足以删除包含element.h的需要

使用这样的部分声明,你只能使用不需要编译器知道任何布局的元素指针。

恕我直言,最好的方法是避免这种循环,因为它们是应该避免的物理耦合的标志。

例如(据我记得)“面向对象的设计启发式” 旨在避免Include Guards的目的,因为它们只掩盖了循环(物理)依赖。

另一种方法是预先解析这样的结构: <代码>

element.h:
struct tree_;
struct element_
  {
    struct tree_ *tree_parent;
    char *name;
  };

tree.h: struct element_; struct tree_ { struct tree_* first_child; struct tree_* next_sibling; int tag; struct element_ *obj; };

前向声明是保证将来会定义结构的结构的方式。

我不喜欢前瞻声明因为它们是多余的和错误的。如果您希望所有声明都在同一个地方,那么您应该使用包含保护的包含和头文件。

当c预处理器发现#include行只是将myheader.h的整个内容放在找到#include行的同一位置时,你应该考虑包含为复制粘贴。

好吧,如果你写包含警卫,myheader.h的代码只会被粘贴一次,而第一个#include被找到。

如果您的程序编译了多个目标文件并且问题仍然存在,那么您应该在目标文件之间使用前向声明(就像使用extern),以便只对所有目标文件保留类型声明(编译器混合同一个表中的所有声明)和标识符必须是唯一的。)

一个简单的解决方案就是没有单独的头文件。毕竟,如果他们彼此依赖,你永远不会使用另一个,那么为什么要将它们分开呢?您可以使用单独的.c文件,它们都使用相同的标头,但提供更集中的功能。

我知道这并没有回答如何正确使用所有花哨的东西的问题,但我发现在寻找类似问题的快速修复时它很有帮助。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top