在学习c++的过程中,我们一定会及其频繁的使用#include这个指令,但是大多数人包括我在初学的时候也只是看书上或者老师说这是包含头文件的预处理指令,有这个你才能进行编程。当然,这个语句及其简单甚至我们不需要过多的理解就能直接用,也可以用的非常顺手,但是随着学习的深入总是会遇到一些与头文件有关的错误,这时就需要我们对#include这个指令有一个直观的了解。
这个指令的作用非常简单,就是复制粘贴。它会把你包含的那个头文件的内容原封不动的粘贴过来(所以不一定要#include .h文件,.cpp文件也行,假如你喜欢做一些奇怪的事情以及你知道在做啥),这就是它的功能,没有想象中的那么神秘。
这样说还是比较模糊,看个例子吧。
首先我们先来设置一下相关的配置,让vs输出一个.i文件以便更好的观察我们实际上编译的代码是什么。
在vs界面上右键点击解决方案,然后进入属性界面
把这个选项改成yes,在点击应用就行了,按ctrl+f7然后在自己的工程下面找到.i后缀的文件,打开后就能看见,
注意我们设置这个是为了便于观察,它能够输出我们实际上要编译的代码,但是它本身会抑制编译,这个该属性的描述里面也可以看到suppress compilation。这意味着我们要实现正常的编译功能还是需要把这个属性选项关闭掉的。好了,我们看一下怎么使用吧。
首先我们在一个cpp文件里面定义一个函数,不用包含什么头文件,就是很普通的函数。
int max(int a, int b)
{
return (a > b ? a : b);
}
按下ctrl+f7进行编译,是可以通过的(用的vs2017),然后我们设置生成该文件再次按下ctrl + f7,然后打开.i文件看一看它在经过预处理过后生成的代码 (这就是为什么不用包含那些常用的库,因为里面有大量的代码)
#line 1 "c:\\users\\cm\\pictures\\mess\\c++ study\\show\\show\\source1.cpp"
int max(int a, int b)
{
return (a > b ? a : b);
}
ok,这个代码和我们写的代码没什么两样,只是多了第一行的路径,这个我们不用管它,我们只看代码主体。
然后我定义一个example.h头文件,里面包含的内容只有一个括号,就像这样
会报错也没关系,我们的关注点不在这里(这个文件会报错但是我编译的是另外一个文件,所以只要编译的那个文件没问题就行),回到刚才的这个cpp文件,把最后的大括号删除,取而代之的是#include刚才的头文件。
//source.cpp
int max(int a, int b)
{
return (a > b ? a : b);
#include"example.h"
然后再进行编译,发现它依然能够通过。
原因很简单,#include把头文件中的 } 粘贴过来了,验证一下,我们生成.i文件,然后打开
//source.i文件
#line 1 "c:\\users\\cm\\pictures\\mess\\c++ study\\show\\show\\source1.cpp"
int max(int a, int b)
{
return (a > b ? a : b);
#line 1 "c:\\users\\cm\\pictures\\mess\\c++ study\\show\\show\\example.h"
}
#line 5 "c:\\users\\cm\\pictures\\mess\\c++ study\\show\\show\\source1.cpp"
一目了然。(不要去注意那些#line1之类的,只需要关注主体部分)
知道了他工作的原理,也就能明白为什么在头文件的开头我们经常需要使用#pragma once了,但我们重复包含某个头文件时,就相当于把头文件里面的代码粘贴了两次,这意味这里面要是有相关函数的定义或者变量的定义,就会造成重复定义了。但是注意,声明是可以重复声明的,函数就直接写函数名,变量的话得加个extern。
为了预防头文件被重复包含,方法很简单,第一种就是在头文件的最开头加上#pragma once,第二种也是非常常见的,就是用#ifndef,#define,#endif这三个语句,譬如我在example.h头文件里有如下代码:
//example.h 文件
#ifndef _EXAMPLE_H
#define _EXAMPLE_H
class example
{
};
#endif
这几个语句的作用非常简单,就是看这个 _EXAMPLE_H字符串是否被定义过,假如是,说明这个头文件已经被包含过了,否则没有被包含过。看看例子,我在主函数文件里面多次包含这个头文件,看看它实际编译的代码是什么
//source.cpp文件
#include"example.h"
#include"example.h"
int main()
{
}
然后查看生成的.i文件
//source.i文件
#line 1 "c:\\users\\cm\\pictures\\mess\\c++ study\\show\\show\\source1.cpp"
#line 1 "c:\\users\\cm\\pictures\\mess\\c++ study\\show\\show\\example.h"
class example
{
};
#line 7 "c:\\users\\cm\\pictures\\mess\\c++ study\\show\\show\\example.h"
#line 2 "c:\\users\\cm\\pictures\\mess\\c++ study\\show\\show\\source1.cpp"
int main()
{
}
这里面只有一个example的定义,原因是我虽然粘贴了两次代码(即#include了两次),但是因为有#ifndef这个命令,导致我第二次的代码内容是不能够体现的,这个结果大家估计能预料到且非常理解(不清楚的话可以查一下这三个预处理指令的作用,然后在电脑上自己敲一下代码),毫无疑问这个代码实际编译是能够通过的,假如我把头文件里面的#ifndef,#define,#endif删掉,再看看它实际生成的代码是啥。
#line 1 "c:\\users\\cm\\pictures\\mess\\c++ study\\show\\show\\source1.cpp"
#line 1 "c:\\users\\cm\\pictures\\mess\\c++ study\\show\\show\\example.h"
class example
{
};
#line 2 "c:\\users\\cm\\pictures\\mess\\c++ study\\show\\show\\source1.cpp"
#line 1 "c:\\users\\cm\\pictures\\mess\\c++ study\\show\\show\\example.h"
class example
{
};
#line 3 "c:\\users\\cm\\pictures\\mess\\c++ study\\show\\show\\source1.cpp"
int main()
{
}
很显然,因为没有那三个预处理指令,导致这里粘贴了两个example的定义,所以当我们编译这样的代码时肯定会报错,这个也是很符合预期的。
Severity Code Description Project File Line Suppression State
Error C2011 'example': 'class' type redefinition show c:\users\cm\pictures\mess\c++ study\show\show\example.h 4
这基本上就是这个指令的作用了。