假设我有两个相互引用的数据结构。我想将它们放入单独的头文件中,如下所示:
\n // datastruct1.h\n #ifndef DATA_STRUCT_ONE\n #define DATA_STRUCT_ONE\n\n #include <datastruct2.h>\n typedef struct DataStructOne_t\n {\n DataStructTwo* two;\n } DataStructOne;\n #endif\nRun Code Online (Sandbox Code Playgroud)\n和
\n // datastruct2.h\n #ifndef DATA_STRUCT_TWO\n #define DATA_STRUCT_TWO\n\n #include <datastruct1.h>\n typedef struct DataStructTwo_t\n {\n DataStructOne* one;\n } DataStructTwo;\n\n #endif\nRun Code Online (Sandbox Code Playgroud)\n我有一个main功能:
#include <datastruct1.h>\n #include <datastruct2.h>\n\n int main() \n {\n DataStructOne* one;\n DataStructTwo* two;\n }\nRun Code Online (Sandbox Code Playgroud)\n然而我的编译器抱怨:
\n$ gcc -I. -c main.c\nIn file included from ./datastruct1.h:4,\n from main.c:1:\n./datastruct2.h:8:2: error: unknown type name \xe2\x80\x98DataStructOne\xe2\x80\x99\n 8 | DataStructOne* one;\n | ^~~~~~~~~~~~~\nRun Code Online (Sandbox Code Playgroud)\n这是为什么?我可以做什么来解决这个问题?
\n为了理解其中的原因,我们需要像编译器一样思考。让我们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看不到。恐慌!发生了什么?为了编译“datastruct2.h”,编译器需要“datastruct1.h”,但#include“datastruct1.h”中的防护措施阻止其内容实际包含在需要的地方。
这种情况是对称的,因此如果我们交换“main.c”中指令的顺序#include,我们会得到相同的结果,但两个文件的角色相反。我们也无法删除防护措施,因为这会导致无限的文件包含链。
看来我们需要“datastruct2.h”出现在“datastruct1.h”之前,并且我们需要“datastruct1.h”出现在“datastruct2.h”之前。这似乎不可能。
#include文件A s 文件B 又s 文件A的情况#include显然是不可接受的。我们需要打破恶性循环。
幸运的是,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
Run Code Online (Sandbox Code Playgroud)
在这种情况下,我们可以用同样的方式重写“datastruct2.h”,消除其对“datastruct1.h”的依赖,在两个地方打破循环(严格来说,这是不需要的,但较少的依赖总是好的)。唉。这并非总是如此。通常只有一种方法可以引入前向声明并打破循环。例如,如果,而不是
DataStructOne* one;
Run Code Online (Sandbox Code Playgroud)
我们有
DataStructOne one; // no pointer
Run Code Online (Sandbox Code Playgroud)
那么前向声明在这个地方不起作用。
那么你就有一个设计问题。例如,如果您使用的是and来代替Both DataStructOne* one;和,则此数据结构无法在 C 或 C++ 中实现。您需要将其中一个字段更改为指针(在 C++ 中:智能指针),或者完全消除它。DataStructTwo* two;DataStructOne one;DataStructTwo two;