几乎每个头文件都应遵循include防护习惯用法:
我的头文件
#ifndef MY_HEADER_FILE_H #define MY_HEADER_FILE_H // 头文件的代码体 #endif
这样可以确保当您#include "my-header-file.h"在多个位置时,不会得到函数,变量等的重复声明。请想象以下文件层次结构:
头文件1.h
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() { // 做点什么 }
此代码有一个严重的问题:的详细内容MyStruct定义了两次,这是不允许的。由于一个头文件包含另一个头文件,因此这将导致难以跟踪的编译错误。如果您改为使用标头后卫进行操作:
头文件1.h
#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() { // 做点什么 }
然后将其扩展为:
#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 // 安全,因为HEADER_1_H是之前#define的。 #define HEADER_1_H typedef struct { … } MyStruct; int myFunction(MyStruct *value); #endif int myFunction2(MyStruct *value); #endif int main() { // 做点什么 }
当编译器到达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() { // 做点什么 }
因此,没有编译错误。
注意:命名头保护器有多种不同的约定。有些人喜欢命名HEADER_2_H_,有些包括项目名称MY_PROJECT_HEADER_2_H。重要的是要确保遵循的约定使项目中的每个文件都有唯一的头保护。
如果标头中未包含结构详细信息,则声明的类型将为不完整或不透明的类型。此类类型很有用,可以向函数用户隐藏实现细节。在许多方面,FILE标准C库中的类型可以视为不透明类型(尽管它通常不是不透明的,因此标准I / O函数的宏实现可以利用结构的内部结构)。在这种情况下,header-1.h可能包含:
#ifndef HEADER_1_H #define HEADER_1_H typedef struct MyStruct MyStruct; int myFunction(MyStruct *value); #endif
请注意,该结构必须具有标签名称(在这里MyStruct-位于标签名称空间中,与typedef name的普通标识符名称空间分开MyStruct),并且{ … }省略。这说“有一个结构类型,struct MyStruct并且有一个别名MyStruct”。
在实现文件中,可以定义结构的详细信息以使类型完整:
struct MyStruct { … };
如果使用的是C11,则可以重复typedef struct MyStruct MyStruct;声明而不会导致编译错误,但是C的早期版本会抱怨。因此,即使在此示例中,如果代码仅使用支持C11的编译器进行编译,它仍然是可选的,即使在此示例中,它仍然是最佳选择。
许多编译器支持#pragma once伪指令,其结果相同:
我的头文件
#pragma once // 头文件代码
但是,#pragma once它不是C标准的一部分,因此如果使用它,则代码的可移植性较差。
一些标头不使用include防护习惯用法。一个特定的例子是标准<assert.h>头。它可以多次包含在一个转换单元中,这样做的效果取决于NDEBUG每次包含标头时是否都定义了宏。您可能偶尔会有类似的要求;这样的情况很少。通常,您的标头应使用include Guard惯用语进行保护。