12

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

树.h:

#include "element.h"

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

和元素.h:

#include "tree.h"

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

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

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

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

4

11 回答 11

29

我认为这里的问题不是缺少包含保护,而是两个结构在定义中需要彼此。所以这是一个类型定义 hann 和 egg 问题。

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

例如

内部树.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 了。

您还应该在头文件周围放置包含防护。

于 2008-09-28T20:39:24.727 回答
9

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

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

#include "element.h"

做:

typedef struct element_ element;

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

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

关于包含守卫的答案是错误的——它们通常是一个好主意,你应该阅读它们并给自己一些,但它们并不能特别解决你的问题。

于 2008-09-28T20:38:49.113 回答
3

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

包括警卫

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

// Your code here

#endif
/* end foo.h */

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

前向声明

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

struct tree;    /* element.h */
struct element; /* tree.h    */
于 2008-09-28T20:31:15.370 回答
2

阅读前向声明

IE。


// 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
于 2008-09-28T20:39:22.073 回答
0

这些被称为“once-only headers”。请参阅http://developer.apple.com/DOCUMENTATION/DeveloperTools/gcc-4.0.1/cpp/Once_002dOnly-Headers.html#Once_002dOnly-Headers

于 2008-09-28T20:30:12.517 回答
0

包含守卫很有用,但不能解决发布者的问题,即对两个数据结构的递归依赖。

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

就像是:

struct element_;
typedef struct element_ element;

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

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

于 2008-09-28T20:41:40.587 回答
0

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

例如(据我所知)“面向对象的设计启发式”旨在避免包含守卫,因为它们只掩盖循环(物理)依赖关系。

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

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

于 2008-09-28T20:46:12.743 回答
0

前向声明是您可以保证会有一个稍后将定义的结构的方式。

于 2009-10-06T11:15:58.637 回答
0

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

您应该将包含视为复制粘贴,当 c 预处理器找到 #include 行时,只需将 myheader.h 的全部内容放在找到 #include 行的相同位置。

好吧,如果您编写包含守卫,则 myheader.h 的代码将仅在找到第一个 #include 的位置粘贴一次。

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

于 2010-11-13T00:52:14.057 回答
0

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

我知道这并不能回答如何正确使用所有花哨的东西的问题,但是当我在寻找类似问题的快速解决方案时,我发现它很有帮助。

于 2017-04-03T03:36:05.907 回答
0

这里有很多答案都提到了“包括警卫”和“前向声明”,但他们都没有真正打算解决 OP 目前面临的问题。第三个“.h”文件绝对不是答案。如果使用得当,“包括守卫”可以打破“#include循环”并最终导致更清洁的项目结构。typedef如果您已经有两个,为什么还要为 s 创建另一个头文件?你的头文件应该是这样的:

/* a.h - dependency of b.h */
#ifndef _A_H
#define _A_H

#include "b.h"

typedef struct a_p {
    b_t *b;
} a_t;

#endif // _A_H
/* b.h - dependency of a.h */
#ifndef _B_H
#define _B_H

typedef struct b_p b_t;

/** 
 * !!!
 * to avoid recursion, only include "a.h" 
 * when "a.h" isn't included before
 */
#ifndef _A_H
    #include "a.h"
    typedef struct b_p {
        a_t a;
    } b_t;
#endif

#endif // _B_H

要使用这两个头文件,您只需要include一个,即无条件包含另一个头文件的那个(在本例中为a.h)。但如果你愿意,你也可以包括“bh”。但无论如何它不会有任何区别(由于前向声明)。

#include "a.h"

int main() {
    a_t aigh;

    return 0;
}

瞧!就是这个!没有多余include的,什么都没有。我们得到了em bois!

于 2020-09-23T15:01:42.553 回答