标题包括警卫

几乎每个头文件都应该遵循 include guard 惯用语:

我的头,file.h

#ifndef MY_HEADER_FILE_H
#define MY_HEADER_FILE_H

// Code body for header file

#endif

这可以确保当你在多个地方使用时,你不会获得函数,变量等的重复声明。想象一下以下文件层次结构:

首标升·小时

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

报头 2.H

#include "header-1.h"

int myFunction2(MyStruct *value);

main.c 中

#include "header-1.h"
#include "header-2.h"

int main() {
    // do something
}

这段代码有一个严重的问题:MyStruct 的详细内容定义了两次,这是不允许的。这将导致难以跟踪的编译错误,因为一个头文件包括另一个头文件。如果你改为使用标题保护:

首标升·小时

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

报头 2.H

#ifndef HEADER_2_H
#define HEADER_2_H

#include "header-1.h"

int myFunction2(MyStruct *value);

#endif

main.c 中

#include "header-1.h"
#include "header-2.h"

int main() {
    // do something
}

然后这会扩展到:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

#ifndef HEADER_2_H
#define HEADER_2_H

#ifndef HEADER_1_H // Safe, since HEADER_1_H was #define'd before.
#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#endif

int myFunction2(MyStruct *value);

#endif

int main() {
    // do something
}

当编译器达到 header- 1.h 的第二个包含时,HEADER_1_H 已经被前一个包含定义。因此,它归结为以下几点:

#define HEADER_1_H

typedef struct {
    …
} MyStruct;

int myFunction(MyStruct *value);

#define HEADER_2_H

int myFunction2(MyStruct *value);

int main() {
    // do something
}

因此没有编译错误。

注意:命名标题保护有多种不同的约定。有些人喜欢把它命名为 HEADER_2_H_,有些人喜欢像 MY_PROJECT_HEADER_2_H 这样的项目名称。重要的是确保你遵循的约定使你的项目中的每个文件都具有唯一的标题保护。

如果结构详细信息未包含在标头中,则声明的类型将是不完整的或不透明的类型 。这些类型可能很有用,隐藏了函数用户的实现细节。出于许多目的,标准 C 库中的 FILE 类型可以被视为不透明类型(尽管它通常不是不透明的,因此标准 I / O 函数的宏实现可以利用结构的内部)。在这种情况下,header-1.h 可能包含:

#ifndef HEADER_1_H
#define HEADER_1_H

typedef struct MyStruct MyStruct;

int myFunction(MyStruct *value);

#endif

请注意,结构必须具有标记名称(此处为 MyStruct - 位于 tags 命名空间中,与 typedef 名称 MyStruct 的普通标识名称空间分开),并且省略了 { … }。这说“有一种结构类型 struct MyStruct,并且有一个别名为 MyStruct”。

在实现文件中,可以定义结构的详细信息以使类型完整:

struct MyStruct {
    …
};

如果你使用的是 C11,则可以重复 typedef struct MyStruct MyStruct; 声明而不会导致编译错误,但早期版本的 C 会抱怨。因此,最好使用 include guard 惯用法,即使在这个例子中,如果代码只使用支持 C11 的编译器进行编译,它也是可选的。

许多编译器都支持 #pragma once 指令,它具有相同的结果:

我的头,file.h

#pragma once

// Code for header file

但是,#pragma once 不是 C 标准的一部分,因此如果你使用它,代码就不那么便携了。

一些标题不使用包含保护语成语。一个具体的例子是标准的 <assert.h> 标题。它可以在单个翻译单元中被多次包括,并且这样做的效果取决于每次包括头部时是否定义宏 NDEBUG。你可能偶尔会有类似的要求; 这种情况很少见。通常,你的标题应该受到包含保护惯用语的保护。