前言
在 C/CPP 中,按照模块来组织一个 Project,会让 Project 更容易阅读和维护。
正确理解宏指令 #include
的处理机制
“#include” 宏指令是在正式编译之前的预处理程序(preprocessing)来处理的,处理方法和 “include” 的英文意思一样:文件包含 - 将指定文件嵌入到指定位置。例如,我们有以下两个文件:DemoInclude.h、DemoInclude.cpp,内容如下:
DemoInclude.h
#ifndef __DEMOINCLUDE_H__
#define __DEMOINCLUDE_H__
void DemoFunc(void);
#endif
DemoInclude.cpp
#include "DemoInclude.h"
int main(void) {
DemoFunc();
return 0;
}
经过预处理(preprocessing)后,以上程序片段被处理为如下程序片段:
#ifndef __DEMOINCLUDE_H__
#define __DEMOINCLUDE_H__
void DemoFunc(void);
#endif
int main(void) {
DemoFunc();
return 0;
}
经过预处理程序,头文件会被真正的包含进
#include
所在的位置!
正确理解头文件中 #ifndef
、#define
、#endif
的用法
头文件的标准结构通常如下:
#ifndef __头文件文件名__
#define __头文件文件名__
// 头文件的具体内容
#endif
Q:为什么要使用上述的标准结构呢?
A:这样的标准结构是为了避免 “头文件嵌套” 带来的重复定义问题。
例如,看以下程序片段,有三个文件:DemoH1.h、DemoH2.h、Demo.cpp
DemoH1.h
void DemoFunc1(void);
DemoH2.h
#include "DemoH1.h"
void DemoFunc2(void);
Demo.cpp
#include "DemoH1.h"
#include "DemoH2.h"
int main(void) {
DemoFunc1();
DemoFunc2();
return 0;
}
根据上节的讲解,我们知道,经过预处理之后,以上程序片段会被处理为如下程序片段:
// #include "DemoH1.h" 被处理为:
void DemoFunc1(void);
// #include "DemoH2.h" 被处理为:
void DemoFunc1(void);
void DemoFunc2(void);
int main(void) {
DemoFunc1();
DemoFunc2();
return 0;
}
很显然,在上面的程序片段中:DemoFunc1()
被重复定义了两次(在第 2 行和第 4 行),编译器会报告重复定义错误。
如果我们按照标准结构改写 DemoH1.h、DemoH2.h,经过预处理程序,以上程序片段会被处理为如下程序片段:
#ifndef __DEMOH1_H__
#define __DEMOH1_H__
void DemoFunc1(void);
#endif
#ifndef __DEMOH1_H__
#define __DEMOH1_H__
void DemoFunc1(void);
#endif
#ifndef __DEMOH2_H__
#define __DEMOH2_H__
void DemoFunc2(void);
#endif
int main(void) {
DemoFunc1();
DemoFunc2();
return 0;
}
接下来,预处理程序将处理 #ifndef
、#define
、#endif
宏指令:
- 第 1 行
#ifndef __DEMOH1_H__
:标识符(identifier)__DEMOH1_H__
是否被定义?如果没有定义,则直到#endif
之前的代码块有效。很显然,__DEMOH1_H__
从来都没有出现过,所以代码段 2-3 行有效。 - 第 2 行
#define __DEMOH1_H__
:定义标识符__DEMOH1_H__
(注意:__DEMOH1_H__
可以为空)。 - 第 6 行
#ifndef __DEMOH1_H__
:标识符__DEMOH1_H__
是否被定义?如果没有定义,则直到#endif
之前的代码块有效。很显然,__DEMOH1_H__
在第 2 行被定义了,所以代码段 7-8 行无效。
经过以上预处理过程,以上代码片段会被处理为如下代码片段:
// #ifndef __DEMOH1_H__
#define __DEMOH1_H__
void DemoFunc1(void);
// #endif
//
// #ifndef __DEMOH1_H__
// #define __DEMOH1_H__
// void DemoFunc1(void);
// #endif
//
// #ifndef __DEMOH2_H__
#define __DEMOH2_H__
void DemoFunc2(void);
// #endif
int main(void) {
DemoFunc1();
DemoFunc2();
return 0;
}
这时,我们发现 DemoFunc()
只被定义了一次,不会出现编译错误。
Q:#ifndef identifier
中的标识符(identifier)为什么要使用 __文件名__
的形式,可以使用其它形式的标识符吗?
A:从上一节的分析来看,我们只需要求 #ifndef identifier
和 #define identifier
成对出现就可以避免重复定义的问题,因此原则上说:identifier
可以是任意形式。但是,要注意的是:不同头文件中的 identifier
不能相同,否则头文件嵌套时,会出现缺失定义的问题(可以自行分析下)。当 Project 很大的时候,如果不按照一定的规则,很难保证不同头文件中的 identifier
唯一。习惯上使用 __文件名__
的形式是因为:同一个 Project 中肯定不会出现同名的头文件,因此很简单的保证了 identifier
唯一的要求。