C头文件循环

时间:2020-03-06 14:51:06  来源:igfitidea点击:

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

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需要包含元素,而element需要包含树。

这是行不通的,因为要定义"树"结构,必须已经知道元素结构,但是要定义元素结构,必须知道树结构。

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

解决方案

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

正确的答案是使用include防护,并使用前向声明。

包括卫兵

/* 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中,而不是:

#include "element.h"

做:

typedef struct element_ element;

这"声明"了类型" element"和" struct element _"(说它们存在),但没有"定义"它们(说它们是什么)。存储指向blah的指针所需要做的就是声明blah,而不是定义它。仅当我们要尊重它(例如阅读成员)时,才需要定义。 " .c"文件中的代码需要执行此操作,但是在这种情况下,标头就不需要这样做。

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

关于包含后卫的答案是错误的,它们通常是一个好主意,我们应该阅读并了解一些内容,但是它们并不能特别解决问题。

阅读有关前向声明的信息。

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

我认为这里的问题不是缺少"包括防护",而是两个结构在定义上彼此需要的事实。因此,这是定义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了。

我们还应该在头文件周围放置include-guards。

包含保护很有用,但不能解决张贴者的问题,即对两个数据结构的递归依赖性。

此处的解决方案是将树和/或者元素声明为头文件中结构的指针,因此我们无需包括.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_
  {
    struct tree_* first_child;
    struct tree_* next_sibling;
    int tag;
    struct element_ *obj;
  };

`

前向声明是一种可以保证以后将定义结构的类型的方式。

我不喜欢前向声明,因为它们是多余的和错误的。如果要将所有声明放在同一位置,则应使用包含和带有保护标志的头文件。

我们应该将include视为复制粘贴,当c预处理程序发现#include行时,只需将myheader.h的全部内容放在找到#include行的相同位置。

好吧,如果我们编写了include防护,则myheader.h的代码将仅粘贴一次找到第一个#include的位置。

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