题
我有几个头文件,归结为:
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文件,它们都使用相同的标头,但提供更集中的功能。
我知道这并没有回答如何正确使用所有花哨的东西的问题,但我发现在寻找类似问题的快速修复时它很有帮助。