预处理指令

转自 http://www.cnblogs.com/mjios/

预处理指令简介

C语言在对源程序进行编译之前,会先对一些特殊的预处理指令作解释(比如之前使用的#include文件包含指令),产生一个新的源程序(这个过程称为编译预处理),之后再进行通常的编译.C语言提供的预处理指令主要有:宏定义文件包含条件编译

宏定义

宏名的有效范围是从定义位置到文件结束。如果需要终止宏定义的作用域,可以用#undef命令

#define PI 3.14

……

#undef PI 

带参数的宏定义

#define 宏名(参数列表字符串

在编译预处理时,将源程序中所有宏名替换成字符串,并且将字符串中的参数用 宏名右边参数列表 中的参数替换

 #define average(a, b) (a+b)/2

 int a = average(104);   

使用注意

1> 宏名和参数列表之间不能有空格,否则空格后面的所有字符串都作为替换的字符串

#define average (a, b) (a+b)/2

int a = average(104);

注意第1行的宏定义,宏名average(a, b)之间是有空格的,于是,第2行就变成了这样:

int a = (a, b) (a+b)/2(104);

这个肯定是编译不通过的

与函数的区别

从整个使用过程可以发现,带参数的宏定义,在源程序中出现的形式与函数很像。但是两者是有本质区别的:

1> 宏定义不涉及存储空间的分配、参数类型匹配、参数传递、返回值问题

2> 函数调用在程序运行时执行,而宏替换只在编译预处理阶段进行。所以带参数的宏比函数具有更高的执行效率

条件编译的概念

在很多情况下,我们希望程序的其中一部分代码只有在满足一定条件时才进行编译,否则不参与编译(只有参与编译的代码最终才能被执行),这就是条件编译

#if 条件1

...code1...

#elif 条件2

...code2...

#else

...code3...

#endif

#if 和 #elif后面的条件一般是判断宏定义而不是判断变量,因为条件编译是在编译之前做的判断,宏定义也是编译之前定义的,而变量是在运行时才产生的、才有使用的意义

举个例子

#include <stdio.h>

#define MAX 11

 

 #if MAX == 0

     printf("MAX0");

 #elif MAX > 0

     printf("MAX大于0");

 #else

     printf("MAX小于0");

 #endif

其他用法

#if defined()#if !defined()的用法

#if 和 #elif后面的条件不仅仅可以用来判断宏的值,还可以判断是否定义过某个宏。比如:

#if defined(MAX)

    ...code...

#endif 

#ifdef#ifndef的使用

* #ifdef的使用和#if defined()的用法基本一致

#ifdef MAX

...code...

#endif

 

这讲介绍最后一个预处理指令---文件包含

1.1种形式#include<文件名>

直接到C语言库函数头文件所在的目录中寻找文件

2.2种形式 #include"文件名"

系统会先在源程序当前目录下寻找,若找不到,再到操作系统的path路径中查找,最后才到C语言库函数头文件所在目录中查找

使用注意

1.#include指令允许嵌套包含,比如a.h包含b.hb.h包含c.h,但是不允许递归包含,比如 a.h 包含 b.hb.h 包含 a.h 

2.使用#include指令可能导致多次包含同一个头文件,降低编译效率

比如下面的情况:

  

 

one.h中声明了一个one函数;在two.h中包含了one.h,顺便声明了一个two函数。(这里就不写函数的实现了,也就是函数的定义)

假如我想在main.c中使用onetwo两个函数,而且有时候我们并不一定知道two.h中包含了one.h,所以可能会这样做:

 

编译预处理之后main.c的代码是这样的:

1 void one();

2 void one();

3 void two();

4 int main ()

5 {

6 

7     return 0;

8 }

可以看出来,one函数被声明了2遍,根本就没有必要,这样会降低编译效率。

为了解决这种重复包含同一个头文件的问题,一般我们会这样写头文件内容:

  

大致解释一下意思,就拿one.h为例:当我们第一次#include "one.h"时,因为没有定义_ONE_H_,所以第9行的条件成立,接着在第10行定义了_ONE_H_这个宏,然后在13行声明one函数,最后在15行结束条件编译。当第二次#include "one.h",因为之前已经定义过_ONE_H_这个宏,所以第9行的条件不成立,直接跳到第15行的#endif,结束条件编译。就是这么简单的3句代码,防止了one.h的内容被重复包含。

这样子的话,main.c中的:

#include "one.h"#include "two.h"

就变成了:

1 // #include "one.h"

 2 #ifndef _ONE_H_

 3 #define _ONE_H_

 4 

 5 void one();

 6 

 7 #endif

 8 

 9 // #include "two.h"

10 #ifndef _TWO_H_

11 #define _TWO_H_

12 

13 // #include "one.h"

14 #ifndef _ONE_H_

15 #define _ONE_H_

16 

17 void one();

18 

19 #endif

20 

21 void two();

22 

23 #endif

2~7行是#include "one.h"导致的10~23行是#include "two.h"导致的。编译预处理之后就变为了:

1 void one();

2 void two();

这才是我们想要的结果


#include 指令后面会跟着一个文件名,预处理器发现 #include 指令后,就会根据文件名去查找文件,并把这个文件的内容包含到当前文件中。被包含文件中的文本将替换源文件中的 #include 指令,就像你把被包含文件中的全部内容拷贝到这个 #include 指令所在的位置一样。所以第一行指令的作用是将stdio.h文件里面的所有内容拷贝到第一行中。

如果被包含的文件拓展名为.h,我们称之为"头文件"(Header File),头文件可以用来声明函数,要想使用这些函数,就必须先用 #include 指令包含函数所在的头文件

#include 指令不仅仅限于.h头文件,可以包含任何编译器能识别的C/C++代码文件,包括.c.hpp.cpp等,甚至.txt.abc等等都可以

也就是说你完全可以将第3~7行的代码放到其他文件中,然后用 #include 指令包含进来,比如:

1> 将第3~7行的代码放到my.txt

2> main.c源文件中包含my.txt文件

编译链接后,程序还是可以照常运行的,因为 #include 的功能就是将文件内容完全拷贝到 #include 指令所在的位置

2.#include可以使用绝对路径

上面的#include "my.txt"使用的是相对路径,其实也可以使用绝对路径。比如#include "/Users/apple/Desktop/my.txt"

 

3.#include <>#include ""的区别

二者的区别在于:当被include的文件路径不是绝对路径的时候,有不同的搜索顺序。

1> 对于使用双引号""include文件,搜索的时候按以下顺序:

· 先在这条include指令的父文件所在文件夹内搜索,所谓的父文件,就是这条include指令所在的文件

· 如果上一步找不到,则在父文件的父文件所在文件夹内搜索;

· 如果上一步找不到,则在编译器设置的include路径内搜索;

· 如果上一步找不到,则在系统的INCLUDE环境变量内搜索

2> 对于使用尖括号<>include文件,搜索的时候按以下顺序:

· 在编译器设置的include路径内搜索;

· 如果上一步找不到,则在系统的INCLUDE环境变量内搜索

Mac系统的include路径有:

· /usr/include

· /usr/local/include

4.stdio.h

我们已经知道#include指令的作用了,可是为什么要在第一行代码包含stdio.h呢?

· stdio.h C语言函数库中的一个头文件,里面声明了一些常用的输入输出函数,比如往屏幕上输出内容的printf函数

· 这里之所以包含 stdio.h 文件,是因为在第5行中用到了在 stdio.h 内部声明的printf函数,这个函数可以向屏幕输出数据,第7行代码输出的内容是:Hello, World!

· 注意:stdio.h里面只有printf函数的声明。前面已经提到:只要知道函数的声明,就可以调用这个函数,就能编译成功。不过想要这个程序能够运行成功,必须保证在链接的时候能找到函数的定义。其实链接除了会将所有的目标文件组合在一起,还会关联C语言的函数库,函数库中就有printf函数的定义。因此前面的程序是可以链接成功的。

5.头文件.h和源文件.c的分工

printf函数一样,我们在开发中会经常将函数的声明和定义写在不同的文件中,函数声明放在.h头文件中,函数定义放在.c源文件中。

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看README.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值