假设我有两个相互引用的数据结构。我想像这样将它们放入单独的头文件中:
// datastruct1.h
#ifndef DATA_STRUCT_ONE
#define DATA_STRUCT_ONE
#include <datastruct2.h>
typedef struct DataStructOne_t
{
DataStructTwo* two;
} DataStructOne;
#endif
和
// datastruct2.h
#ifndef DATA_STRUCT_TWO
#define DATA_STRUCT_TWO
#include <datastruct1.h>
typedef struct DataStructTwo_t
{
DataStructOne* one;
} DataStructTwo;
#endif
我有一个 main
函数:
#include <datastruct1.h>
#include <datastruct2.h>
int main()
{
DataStructOne* one;
DataStructTwo* two;
}
但是我的编译器提示:
$ gcc -I. -c main.c
In file included from ./datastruct1.h:4,
from main.c:1:
./datastruct2.h:8:2: error: unknown type name ‘DataStructOne’
8 | DataStructOne* one;
| ^~~~~~~~~~~~~
这是为什么呢?我该怎么做才能解决这个问题?
最佳答案
为什么?
为了理解原因,我们需要像编译器一样思考。让我们在分析 main.c
时这样做逐行。编译器会做什么?
-
#include <datastruct1.h>
: 把“main.c”放在一边(压入正在处理的文件栈),切换到“datastruct1.h” -
#ifndef DATA_STRUCT_ONE
: 嗯,这还没有定义,让我们继续。 -
#define DATA_STRUCT_ONE
: 好的,定义了! -
#include <datastruct2.h>
: 将“datastruct1.h”放在一边,切换到“datastruct2.h” -
#ifndef DATA_STRUCT_TWO
: 嗯,这还没有定义,让我们继续。 -
#define DATA_STRUCT_TWO
: 好的,定义了! -
#include <datastruct1.h>
: 将“datastruct2.h”放在一边,切换到“datastruct1.h” -
#ifndef DATA_STRUCT_ONE
:现在已经定义,所以直接转到#endif
. -
(end of "datastruct1.h")
:关闭“datastruct1.h”并从填充堆栈中弹出当前文件。我在做什么?啊,“datastruct2.h”。我们从离开的地方继续。 -
typedef struct DataStructTwo_t
好的,开始结构定义 -
DataStructOne* one;
等等,什么是DataStructOne
? 我们没见过吧? (查看已处理行的列表)不,不DataStructOne
洞察力。 panic !
发生了什么事?为了编译“datastruct2.h”,编译器需要“datastruct1.h”,但是 #include
“datastruct1.h”中的守卫阻止其内容实际包含在需要的地方。
情况是对称的,所以如果我们调换 #include
的顺序“main.c”中的指令,我们得到相同的结果,但两个文件的角色相反。我们也不能删除守卫,因为那样会导致无限文件包含链。
看来我们需要“datastruct2.h”出现在“datastruct1.h”之前并且我们需要“datastruct1.h”出现在“datastruct2.h”之前。这似乎不可能。
什么?
文件A的情况#include
s文件B依次为#include
s 文件 A 显然是 Not Acceptable 。我们需要打破恶性循环。
幸运的是,C 和 C++ 有前向声明。我们可以使用这个语言特性来重写我们的头文件:
#ifndef DATA_STRUCT_ONE
#define DATA_STRUCT_ONE
// No, do not #include <datastruct2.h>
struct DataStructTwo_t; // this is forward declaration
typedef struct DataStructOne_t
{
struct DataStructTwo_t* two;
} DataStructOne;
#endif
在这种情况下,我们可以用同样的方式重写“datastruct2.h”,消除它对“datastruct1.h”的依赖,在两个处打破循环(严格来说,这是不需要的,但较少的依赖总是好的)。唉。这并非总是如此。通常只有一种方法可以引入前向声明并打破循环。例如,如果,而不是
DataStructOne* one;
我们有
DataStructOne one; // no pointer
那么前向声明在这个地方是行不通的。
如果我无法在任何地方使用前向声明怎么办?
那么你有一个设计问题。例如,如果不是 both DataStructOne* one;
和 DataStructTwo* two;
你有DataStructOne one;
和 DataStructTwo two;
,那么这个数据结构在 C 或 C++ 中是不可实现的。您需要将其中一个字段更改为指针(在 C++ 中:智能指针),或者完全消除它。
关于c++ - 什么是循环包含依赖项,为什么不好,我该如何解决?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70350116/