180 likes | 274 Views
第 九 章 预 处 理 命 令. 主要内容. 一、什么是预处理 二、宏定义 三、 “ 文件包含 ” 处理 四、条件编译(不讲). 所谓预处理是指,在对源程序进行编译之前,先对源程序中的预处理命令( 主要指宏定义命令、文件包含命令和条件编译命令 )进行处理;然后再将处理的结果,和源程序一起进行编译,以得到目标代码。. 编译预处理. 编译. 连接. 执行. 一、什么是预处理. # define # include. 宏定义 文件包含 条件编译. (一)不带参数的宏定义 (二)带参数宏定义. 二、宏定义. (一)不带参数的宏定义(简单替换).
E N D
主要内容 一、什么是预处理 二、宏定义 三、“文件包含”处理 四、条件编译(不讲)
所谓预处理是指,在对源程序进行编译之前,先对源程序中的预处理命令(主要指宏定义命令、文件包含命令和条件编译命令)进行处理;然后再将处理的结果,和源程序一起进行编译,以得到目标代码。所谓预处理是指,在对源程序进行编译之前,先对源程序中的预处理命令(主要指宏定义命令、文件包含命令和条件编译命令)进行处理;然后再将处理的结果,和源程序一起进行编译,以得到目标代码。 编译预处理 编译 连接 执行 一、什么是预处理 #define #include 宏定义 文件包含 条件编译
(一)不带参数的宏定义 (二)带参数宏定义 二、宏定义
(一)不带参数的宏定义(简单替换) 用标识符来代表一个字符串(给字符串取个名字)。C语言用“#define”进行宏定义。C编译系统在编译前将这些标识符替换成所定义的字符串。 概念 格式 #define 标识符 字符串 #define PI 3.1415926 相关概念 宏名: 宏定义中的标识符称为“宏名”。 宏调用:在程序中用宏名替代字符串称为“宏调用”。 宏展开:在预编译时将宏名替换成字符串的过程称 为“宏展开”。
输入圆的半径,求圆的周长、面积和球的体积。要求使用无参宏定义圆周率。输入圆的半径,求圆的周长、面积和球的体积。要求使用无参宏定义圆周率。 例 1 #include <stdio.h> #define PI 3.1415926 /*宏定义PI是宏名,用来替换常数3.1415926 */ void main() { float radius,length,area,volume; printf("Input a radius: "); scanf("%f",&radius); length=2*PI*radius; /*宏调用,引用无参宏求周长*/ area=PI*radius*radius; /*宏调用,引用无参宏求面积*/ volume=PI*radius*radius*radius*3/4; /*宏调用,引用无参宏求体积*/ printf("length=%.2f,area=%.2f,volume=%.2f\n", length, area, volume); } 宏展开: length=2*3.1415926*radius; area= 3.1415926*radius*radius; volume= 3.1415926*radius*radius*radius*3/4;
1、宏名遵循标识符规定,习惯用大写字母表示,以便区别普通的变量。 2、#define之间不留空格,宏名两侧空格(至少一个)分隔。 3、宏定义字符串不要以分号结束,否则分号也作为字符串的一部分参加展开。从这点上看宏展开实际上是简单的替换。 宏展开 说明 #define PI 3.14; area=PI*r*r; area=3.14;*r*r;
4、宏定义是预处理指令,与定义变量不同,它只是进行简单的字符串替换,不分配内存。4、宏定义是预处理指令,与定义变量不同,它只是进行简单的字符串替换,不分配内存。 5、宏定义用宏名代替一个字符串,并不管它的数据类型是什么,也不管宏展开后的词法和语法的正确性,只是简单的替换。是否正确,编译时由编译器判断。 例如: #define PI 3.I4 照样进行宏展开(替换),是否正确,由编译器来判断。 说明
6、#define命令出现在程序中函数的外面,宏名的有效范围从定义命令开始直到本源程序文件结束。可以通过#undef终止宏名的作用域。6、#define命令出现在程序中函数的外面,宏名的有效范围从定义命令开始直到本源程序文件结束。可以通过#undef终止宏名的作用域。 G的有效范围 PI的有效范围 说明 #define G 9.8 #define PI 3.14 int f1() { … … } #undef G void main() { … … }
7、宏定义时,可以引用已定义的宏名,可以层层置换。若宏名出现在双引号“”括起来的字符串中时,将不会产生宏替换。7、宏定义时,可以引用已定义的宏名,可以层层置换。若宏名出现在双引号“”括起来的字符串中时,将不会产生宏替换。 宏展开 说明 #define R 3.0 #define PI 3.14 #define L 2*PI*R #define S PI*R*R void main() { printf(“L=%f\nS=%f\n”,L,S); } printf(“L=%f\nS=%f\n”,2*3.14*3.0 , 3.14*3.0*3.0);
宏展开 (二)带参数的宏定义 带参数的宏定义不只是进行简单的字符串替换,还要进行参数替换。 概念 类似函数头,但是没有类型说明,参数也不要类型说明 格式 #define 宏名(参数表) 字符串 按照#define命令行中指定的“字符串”从左到右进行置换(扫描置换)。如果串中包含宏定义中的形参,则将程序中相应的实参代替形参,其它字符原样保留,形成了替换后的字符串。 展开置换规则 #define S(a,b) a*b area=S(3,2); 举例 area=3*2;
宏展开 area=3.14*a*a; 例 2 用带参数的宏定义表示圆的面积。 #define PI 3.14 #define S(r) PI*r*r void main() { float a,area; a=3.6; area=S(a); printf(“r=%f\narea=%f\n”,a,area); }
1、正因为带参宏定义本质还是简单字符替换(除了参数替换),所以容易发生错误。1、正因为带参宏定义本质还是简单字符替换(除了参数替换),所以容易发生错误。 例:#define S(r) 3.14*r*r area=S(a+b); 宏展开 说明 不再是求面积了。 area=3.14*a+b*a+b; 2、定义带参数宏时还应该注意宏名与参数表之间不能有空格。有空格就变成了不带参数的宏定义。 例:#define S(r) 3.14*r*r 此时S是符号常量(不带参的宏名),它代表字符串“(r) 3.14*r*r”。
说明 3、带参数的宏定义与函数的本质区别 1)函数调用时,先求表达式的值,然后将值传递给形参;带参宏展开只在编译时进行的简单字符置换。 2)函数调用是在程序运行时处理的,给形参分配临时的内存单元;而宏展开是在编译时进行的,展开时并不给形参分配内存,也不进行“值传递”,也没有“返回值”的概念。 3)函数的形参要定义类型,且要求形参、实参类型一致。宏不存在参数类型问题。 4)宏占用的是编译时间,函数调用占用的是运行时间。在多次调用时,宏使得程序变长,而函数调用不明显。
1、程序中的常量可以用有意义的符号代替,程序更加清晰,容易理解(易读)。1、程序中的常量可以用有意义的符号代替,程序更加清晰,容易理解(易读)。 2、常量值改变时,不用在整个程序中查找修改,只要改变宏定义就可以。比如,提高PI精度值。 3、带参数宏定义比函数调用具有更高的时间效率,因为相当于代码的直接嵌入。(空间效率:多次调用占用空间较多,一次调用没有什么影响)。 总结:使用宏的优点
file1.c file1.c file2.c 包含 #include <file2.c> B B A A 三、“文件包含”处理 所谓“文件包含”处理是指一个源文件可以将另外一个源文件的全部内容包含进来,即将另外的文件包含到本文件之中。 概念 #include “文件名”或 #include <文件名> 格式 示意图
1、被包含的文件常常被称为“头文件”(#include一般写在文件的开头)。头文件常常以“.h”为扩展名(也可以用其它的扩展名,.h只是习惯或风格)。 2、一条#include只能包含一个头文件,如果要包含多个头文件,使用多条#include命令。 3、在一个被包含的文件中又可以包含另一个被包含文件,即文件包含是可以嵌套的。 4、被包含的头文件可以用“”括起来,也可以用<>括起来。区别在于: “”先在用户当前目录查找头文件,如果没找到,再到存放C库函数头文件的目录中查找;而<>直接到存放C库函数头文件的目录中查找。 一般地说,使用双引号比较保险。 5、习惯上,用户头文件一般在用户目录下,所以常常用“”;系统库函数的头文件一般在系统指定目录下,所以常常用<>。 说明
作 业 P199: 9.1, 9.3, 9.5, 9.7