转自 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(10, 4);
使用注意
1> 宏名和参数列表之间不能有空格,否则空格后面的所有字符串都作为替换的字符串
#define average (a, b) (a+b)/2
int a = average(10, 4);
注意第1行的宏定义,宏名average跟(a, b)之间是有空格的,于是,第2行就变成了这样:
int a = (a, b) (a+b)/2(10, 4);
这个肯定是编译不通过的
与函数的区别
从整个使用过程可以发现,带参数的宏定义,在源程序中出现的形式与函数很像。但是两者是有本质区别的:
1> 宏定义不涉及存储空间的分配、参数类型匹配、参数传递、返回值问题
2> 函数调用在程序运行时执行,而宏替换只在编译预处理阶段进行。所以带参数的宏比函数具有更高的执行效率
条件编译的概念
在很多情况下,我们希望程序的其中一部分代码只有在满足一定条件时才进行编译,否则不参与编译(只有参与编译的代码最终才能被执行),这就是条件编译。
#if 条件1
...code1...
#elif 条件2
...code2...
#else
...code3...
#endif
#if 和 #elif后面的条件一般是判断宏定义而不是判断变量,因为条件编译是在编译之前做的判断,宏定义也是编译之前定义的,而变量是在运行时才产生的、才有使用的意义
举个例子
#include <stdio.h>
#define MAX 11
#if MAX == 0
printf("MAX是0");
#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.h,b.h包含c.h,但是不允许递归包含,比如 a.h 包含 b.h,b.h 包含 a.h。
2.使用#include指令可能导致多次包含同一个头文件,降低编译效率
比如下面的情况:
在one.h中声明了一个one函数;在two.h中包含了one.h,顺便声明了一个two函数。(这里就不写函数的实现了,也就是函数的定义)
假如我想在main.c中使用one和two两个函数,而且有时候我们并不一定知道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源文件中。