预处理(编译预处理)
预处理命令:C语言中以符号“#”开头的命令
示例:#define... #include... #ifdef...
含义:
1.在对程序进行编译之前,根据预处理命令对程序进行相应处理。
2.经过预处理后编译器才可以对程序进行编译等处理,得到可供执行的目标代码。
示意图:
解析:
如图,源程序经过编译和连接生成可执行文件。而预处理命令将在编译之前被执行。
宏定义
释义:可以用#define命令将一个指定的标识符(即宏名)来代表一个字符串
分类:
不带参数:
语法:#define 标识符 字符串
应用法则:原样替换
典型应用:符号常量
代码示例:
#include <stdio.h>#include <stdlib.h>/*这个程序用来测试预处理中的不带参数的宏定义*/#define PI 3.1415926int main(){ float r,l,s; PRintf("please enter r:"); scanf("%f",&r); l=2*PI*r; s=r*r*PI; printf(" l: %f /n s: %f /n",l,s); return 0;}结果:
解析:
定义PI为3.1415926之后,在编译前将把代码段中所有PI都替换成3.1415926,之后再生成目标文件。这种方式能降低出错的概念,也能提高程序的可读性。
注意:绝对不要用 #define PI 3.1415926;这样的形式!因为宏定义的法则是原样替换,所以会把分号也原样代入程序!在上述的示例程序中l的计算方程就会变成l=2*3.1415926;*r;一个语句中出现两个分号,就会带来致命的差错!
带参数:
语法:#define 宏名(参数表) 字符串
例:
#define S(a,b) a*b...area=S(2,4);解析:此处area=S(2,4)将在编译前被解析为:area=2*4
代码示例:
#include <stdio.h>#include <stdlib.h>/*这个程序用来测试预处理中的带参数的宏定义*/#define S(a,b) a*bint main(){ printf("矩形1面积;%d/n",S(2,4)); printf("矩形2面积:%.2f/n",S(2.3,4.5)); return 0;}结果:
解析:
如上,程序成功计算出了结果。使用带参数的宏定义同样能降低出错的概念。同时大大提升代码的可读性。
深度理解:
带参数宏定义和函数的区别:
1.函数在编译后会生成目标代码,且在程序运行到调用行,会跳转入函数中继续执行。而带参数的宏定义将在编译前被替换成一个常量表达式。在目标代码中也不会出现任何痕迹。
2.函数的参数、返回值类型都是有严格定义的。而宏定义只是进行机械的替换,参数以及其计算的结果的数据类型都需要自行把握。
带参数宏定义的易错点:
代码示例:
#include <stdio.h>#include <stdlib.h>/*这个程序用来测试预处理中的带参数的宏定义的性质*/#define PI 3.1415926#define S1(r) PI*r*r#define S2(r) PI*(r)*(r)int main(){ float a,b; a=1; b=2; printf("test1:/nr=%.2f,area=%.2f/n",a+b,S1(a+b)); printf("test2:/nr=%.2f,area=%.2f/n",a+b,S2(a+b)); return 0;}结果:
解析:
1.如上,S1和S2看似差别不大。但得到的结果却完全不同。
2.宏定义的法则为原样替换,所以对于S1(1+2)将被替换为:PI*1+2*1+2。可以看到,因为计算符号优先级的原因,算式完全扭曲了我们希望得到的结果。
3.S2所做的是,给每个参数都带上一个小括号,这就解决了S1所遇到的问题,并能够计算出正确的结果。事实上,这也是代码规范化的一部分。认识的这一点之后,在使用宏定义时,应当尽量给每个参数都加上小括号。以减少错误的发生。
文件包含
释义:一个源文件可以将另外一个源文件的全部内容包含进来。
语法: #include "文件地址" 或 #include <文件地址>
图示:
如图,在file1.c中加入#include "file2.c"效果相当于将file2.c中所有的代码拷贝至当前位置。
应用:将源文件与头文件分离,提高代码重用性。具体例子在:http://blog.csdn.net/aketoshknight/article/details/54837779大型程序基础---调用外部头文件部分
特点——节省程序设计人员的重复劳动:
1.将常用的一组固定常量的定义组成一个文件——方便,易修改。
2.库函数的开发者将对被调用的函数的原型声明写入头文件,程序员只需要#include<头文件> ——大大简化了程序。
3.一行#include ,相当于写几十、几百,甚至更多行内容,提高了效率。
起源——模块化程序设计的产物:
1.便于多个程序员分别编程。
2.将公用的符号常量或宏定义等可单独组成一个文件,在其他文件的开头用包含命令包含该文件即可使用。
3.公用的声明只写一次,除节省时间,更可观的是减少出错。
include 命令的两种形式:
1.#include <文件名>
例:#include <stdio.h>
特点:编译器将在系统目录(不同的操作系统可能有不同的系统目录,也可以在编译器的设置中自行修改系统目录的地址)中寻找要包含的文件,如果无法找到该文件,则给出出错信息。
注:对于系统提供的头文件,既可用尖括号形式,也可用双撇号形式,但一般用尖括号形式的效率更高。
2.#include "文件名"
例:#include "mymodule.h"
特点:编译器先按双引号中指出的文件路径和文件名查找(如果给出了E:test.c这样形式的路径,便会去该处寻找。如果如上所示只给出文件名,将默认在用户当前目录(程序所在的目录)中寻找),若找不到,再去系统目录中查找。若仍旧无法找到,则给出出错信息。
注:若要包含的是用户自己编写的文件,一般保存在程序目录中,因此宜用双撇号形式。
GCC编译器中的头文件和库函数:
头文件:
如上,MinGW便是一种GCC编译器,其中的include文件夹里存放着大量系统头文件可供引用。
库函数:
如上,在MinGW的lib文件夹中,存放着大量的.o目标文件,以及多种.o目标文件打包而成的.a文件(在代码连编的连接阶段,便是使用这里的文件和程序源文件的目标代码进行连接的)。
注:以上图示是GCC编译器的体系,而在VC++及VS中,目标文件将打包成.lib或.dll文件,而非.a文件。
条件编译
释义:根据需要,只编译程序中的某一部分。
常用形式1:
//define 标识符#ifdef 标识符 程序段1#else 程序段2#endif解析:ifdef可看成if define的简写。当所指定的标识符已经被#define命令定义过,则在程序编译阶段只编译程序段1,否则编译程序段2。#endif用来限定#ifdef命令的范围,其中#else部分可以省略。
常用形式2:
//define 标识符#ifndef 标识符 程序段1#else 程序段2#endif解析:ifndef可看成if not define的简写。和第一种正好相反。
常用形式3:
#if 常量表达式 程序段1#else 程序段2#endif解析:和常用的if else条件选择结构的区别在于增加了代表预处理的#。如果常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。因此可以使程序在不同条件下完成不同的功能。
代码示例1:
#include <stdio.h>#include <stdlib.h>/*这个程序用来测试预处理中的条件编译*///宏定义常量DEBUG#define DEBUGint main(){ int x=1,y=2; //ifdef 既if define如果宏定义了XX #ifdef DEBUG printf("x=%d,y=%d/n",x,y);//当宏定义DEBUG存在,这句代码将被编译。而如果宏定义DEBUG不存在,这句代码将不被编译。 #endif // DEBUG printf("x*y=%d/n",x*y); return 0;}结果1:代码示例2:
#include <stdio.h>#include <stdlib.h>/*这个程序用来测试预处理中的条件编译*///宏定义常量DEBUG//#define DEBUGint main(){ int x=1,y=2; //ifdef 既if define如果宏定义了XX #ifdef DEBUG printf("x=%d,y=%d/n",x,y);//当宏定义DEBUG存在,这句代码将被编译。而如果宏定义DEBUG不存在,这句代码将不被编译。 #endif // DEBUG printf("x*y=%d/n",x*y); return 0;}结果2:
解析:
1.如上示例,通过改变宏定义,运用#ifdef 即可控制代码中的某一部分是否被编译。
2.虽然使用if else条件判断也能实现类似的效果,但此处的条件判断是预处理的一种。既这些代码将在源文件编译前就被执行,并且在编译后的.o目标文件中将不会出现这些代码。
3.这种方式适合在调试程序时使用,当调试完毕时,只需要取消对DEBUG的宏定义,即可一次性消除大量调试用代码对程序的影响。
代码示例3:
#include <stdio.h>#include <stdlib.h>/*这个程序用来测试预处理中的条件编译*/#define R 1int main(){ float c,r,s; printf("input a number:/n"); scanf("%f",&c); #if R r=3.14159*c*c; printf("area of round is:%f/n",r); #else s=c*c; printf("area of square is:%f/n",s); #endif // R return 0;}结果:
解析:
套用if else的使用经验,可以十分容易的理解。
深入理解:
文件包含的弊端:
假设个文件A.h B.c C.c
A.h:
int test=10;....B.c:include "A.h"int main(){ ....}C.c:include "A.h"include "B.c"int main(){ ....}当编写较大的程序时,常会出现此种交叉包含的情况。这样,在C.c中就会出现A.h被多次包含的问题。既,A.h中的test变量被多次定义。导致编译出错。解决方案:
A.h:
#ifndef MYHEADER#define MYHEADER 1 #define PI 3.14 int NUM=3 .....#endif解析:1.如上,使用条件编译之后,完美的解决了这个问题。2.这是一种编码规范的标准,为了减少代码的BUG,应当用这种方式定义自己的头文件。
新闻热点
疑难解答