#include其实并不是一个非常聪明的机制——直接全文复制,也不管包含了多少用不着的代码;你也不甚清楚你包含的代码中有什么牛鬼蛇神,会不会碰巧撞上了math.h中的y1;假如处理不当,还可能惹来重复定义等令人头秃的麻烦……
在此,我列举一些初次深入了解#include时可能遇到的困扰,并加以说明。
套娃
事情开始于这样的代码:
1 | // A.h |
1 | // B.h |
1 | // main.cpp |
我们在类A中设置了B类型的成员变量,因此需要#include "B.h"。**然而,出于某种需求,我们还希望在类B中保留对应的A的指针,因此还需#include "A.h"。**看起来顺理成章。
可是,当我们编译时,g++报了错:
1 | B.h:6:5:error: 'A' does not name a type |
是在B.h中报了找不到类型A的错。
奇怪,我们明明在B.h中包含了A.h啊……
探因
我们将目光聚焦到A.h上——原来,A.h标上了#pragma once。也就是说,假如A.h之前已经被包含过了,那么这次就不会再包含它了。再一看main.cpp,确实,A.h早已被包含过了。
破案了!
好,我们将A.h中的#pragma once去掉总行了吧?还不行,这次又报找不到类型B了。
那就把B.h中的#pragma once也去掉吧……停下来!不然那编译器的报错……太美……
不过,至此,这背后的原因已可见端倪——套娃include。C++的include最忌讳的就是套娃了。如果不加#pragma once等处理,则头文件就会永无止境地包含下去;如果加了,那你写代码时可能以为自己include过了,实际上却被编译器拦下了。
总之,这种循环包含的行为是不可取的,在实际编程中应当避免。
解决
那么,应当如何修改代码,才能既满足需求,又不出现套娃的现象呢?
在动手之前,先想想,是否真的需要在B中保留A的指针。因为,这种情况的发生,很有可能意味着你的代码设计时耦合度有些高,才会剪不断理还乱。如果能重新设计代码,让B干脆不依赖A,那是最好的。
不过,如果这一需求不可避免呢?那也有办法:
1 | // B.h |
我们在B.h中不去#include "A.h",而是声明class A,供B使用,具体的细节则在A.h中给出。这样,既免去了循环包含,又能够在类B中用到类A。
至此,“套娃”的问题暂告一段落。下面,再简单提一下#pragma once和#ifndef...的事。
重复定义
我们知道,在C++中,对同一个名称,声明可以多次,但定义只能一次。为此,我们需要引入一些保证单次包含的机制,来防止因多次包含同一头文件而造成的重复定义。
#pragma once和#ifndef...的用法,在课件上都有写到。这里,对使用过程中可能遇到的疑惑和误区简单说明一下。
#ifndef XXX含义的理解
#ifndef XXX和#endif配套,可以理解为if not defined XXX,则……,end if。而在解析……所示的代码之前,需要先#define XXX,从而下次解析到这一头文件时,因为宏定义过XXX了,ifndef条件不满足,就不再解析……部分的代码了,从而保证了单次包含。
#ifndef XXX插入的位置
合理的使用方法,应当是#ifndef XXX和#define XXX置于文件的开头而#endif置于文件的末尾,这样才能保证整个文件只被包含一次。
我之前见到过这样的写法:
1 |
|
这就违背保证整个文件只被包含一次的初衷了。假如这一头文件被包含多次,那也会造成Test的重复定义。
(当然,我个人以为出现这样的错误也与课件上只给了用法没给示例有关。)
#pragma once和#ifndef...的区别
#pragma once可以简单快捷地保证物理上的这一文件只被包含一次,不过缺点在于一些编译器可能不支持。(当然,越来越多的编译器已经支持这一功能了。)
#ifndef XXX则是从代码层面保证单次包含,且类似写法可以在其它场合有一些灵活的运用。缺点在于你需要保证不同头文件的XXX不要撞车,否则也会导致预期之外的结果。(当然,许多IDE会为新建的.h文件自动加上#ifndef...等语句,可以省去不少麻烦。)
写在最后
读到这里,或许你对#include的机制更加不理解了还有一些困惑。也许,你很想亲自看到,编译器对这些带#的语句到底做了些什么。
这时,我们来了解一下g++的预编译指令。例如:
1 | g++ -E main.cpp -o main.i |
-E表示当前的任务是对main.cpp进行预编译。预编译的一个任务就是将这些带#的宏命令进行处理,比如#include的内容会在预编译时展开。这时,你就能看到那些头文件到底是谁先谁后了。
本文链接:https://www.unidy.cn/articles/include/