C头文件循环

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

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; 

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

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

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

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

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

例如

在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; 

这“声明”类型“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 

这些被称为“一次性标题”。 请参阅http://developer.apple.com/DOCUMENTATION/DeveloperTools/gcc-4.0.1/cpp/Once_002dOnly-Headers.html#Once_002dOnly-Headers

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

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

就像是:

 struct element_; typedef struct element_ element; 

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

通过这样的部分声明,您只能使用不需要编译器了解布局的元素指针。

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

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

另一种方法是预先声明这样的结构:

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

tree.h中:
struct element_;
结构树_
{
struct tree_ * first_child;
struct tree_ * next_sibling;
int标签;
struct element_ * obj;
};

前向声明是你可以保证将有一个将在后面定义的结构的tyoe的方式。

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

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

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

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

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

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