C语言程序设计 教学课件 ppt 作者 丁峻岭 等 第八章

上传人:E**** 文档编号:89381689 上传时间:2019-05-24 格式:PPT 页数:35 大小:215.50KB
返回 下载 相关 举报
C语言程序设计 教学课件 ppt 作者 丁峻岭 等 第八章_第1页
第1页 / 共35页
C语言程序设计 教学课件 ppt 作者 丁峻岭 等 第八章_第2页
第2页 / 共35页
C语言程序设计 教学课件 ppt 作者 丁峻岭 等 第八章_第3页
第3页 / 共35页
C语言程序设计 教学课件 ppt 作者 丁峻岭 等 第八章_第4页
第4页 / 共35页
C语言程序设计 教学课件 ppt 作者 丁峻岭 等 第八章_第5页
第5页 / 共35页
点击查看更多>>
资源描述

《C语言程序设计 教学课件 ppt 作者 丁峻岭 等 第八章》由会员分享,可在线阅读,更多相关《C语言程序设计 教学课件 ppt 作者 丁峻岭 等 第八章(35页珍藏版)》请在金锄头文库上搜索。

1、第8章 编译预处理,8.1 宏定义(#define) 8.2 文件包含(#include) 8.3 条件编译 课后,为了扩展C语言的编程环境,提高编程质量与技巧,C语言提供了编译预处理的功能。所谓“编译预处理”,是C编译系统中的预处理程序按源程序中的预处理命令进行的一些预加工。 C语言提供了宏定义、文件包含和条件编译三种主要预处理命令。预处理命令均以“#”打头,末尾不加分号。C语言在编译之前,先对这些特殊的命令进行预处理(即当预处理程序遇到以字符“#”开头的一行时,就按预处理命令形式执行有关功能),然后再将预处理的结果与源程序一起编译、处理,以得到目标代码。 C语言的预处理命令可以出现在程序的

2、任意位置,其作用范围是自出现点到所在源程序的末尾或由宏命令指定的终止位置。 编译预处理是C语言的一个重要特点。它能改善程序设计的环境,有助于编写易移植、易调试的程序,也是模块化程序设计的一个重要工具。,8.1 宏定义(#define) 宏定义是用预处理命令#define指定的预处理。下面分别对不带参宏定义与带参宏定义加以介绍。 8.1.1 不带参宏定义 1不带参宏定义的形式 在C程序中可以用#define命令定义不带参宏定义: #define 宏名 宏体 其中宏名与宏体均为字符串。预处理时,将把程序中该宏定义之后的所有宏名用宏体替换。符号常数的定义就是这种宏定义的一种应用。比如: #defin

3、e PI 3.14159265 它的作用是用宏名PI来代替宏体“3.14159265”这个字符串。在预处理时,把程序中在该宏定义以后的所有PI都用“3.1415926”代替。通常把预编译时将宏名替换成宏体的过程称为“宏展开”。,【例8.1】计算圆的周长和面积。 #define PI 3.14159265 #define RADIUS 2.0 double circum() return(2.0*PI*RADIUS); double area() return(PI*RADIUS*RADIUS); main() circum();area(); 经过预处理后将形成如下的源文件: double c

4、ircum() return(2.0*3.14159265*2.0); double area() return(3.14159265*2.0*2.0); main() circum();area(); 这是一种简单的字符替代,不进行任何计算。,2使用宏定义的优点 (1)提高了程序的可读性。观察文件中预处理前后的语句: return(2.0*PI*RADIUS); return(2.0*3.1415926536*2.0); 第一句显然比第二句的可理解性要好。 (2)比较容易修改参数值。如要将RADIUS的值由2.0修改为3.0,只要在#define命令中修改一处便可。而在不使用宏定义的文件中,

5、这种修改将比较麻烦。 3宏定义的使用特点 (1)宏名一般习惯用大写字母表示,以与变量名相区别。当然,也可以用小写字母。 (2)宏体不仅可以是字符串常数,也可以是表达式或语句组成的字符串。,【例8.2】计算圆的周长和面积。 #define PI 3.14159265 #define RADIUS 2.0 #define CIRCUM return(2.0*PI*RADIUS); #define AREA return(PI*RADIUS*RADIUS); double circum() CIRCUM double area() AREA main() circum();area(); 宏定义应先

6、定义后使用,要注意在程序中的顺序。,【例8.3】宏定义在程序中的顺序应用举例。 #define DY printf #define HC “n“ #define A “%d“ #define A1 A HC #define A2 A A HC #define A3 A A A HC #define A4 A A A A HC main() int a=1,b=2,c=3,d=4; DY(A1,a); DY(A2,a,b); DY(A3,a,b,c); DY(A4,a,b,c,d); 运行结果为: 1 1 2 1 2 3 1 2 3 4,(3)用#define命令还可以把多个语句定义为宏。例如:

7、 #define PRA printf(“%d“,12);putchar(n); (4)宏定义不是C语句,不必在行末加分号。如果加了分号则会连分号一起进行替换。例如: #define PI 3.14159265; #define RADIUS 2.0; : area=PI*RADIUS*RADIUS; 经宏展开后,该语句为: area=3.1415926;*2.0;*2.0; 将出现语法错误。 (5)#define命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束或由宏命令#undef指定的终止位置。,(6)可以用#undef命令终止宏定义的作用域。例如: #define

8、ZJ 9.8 main() ZJ的 有效 范围 #undef ZJ (7)在进行宏定义时,可以引用已定义的宏名,可以层层替换。 【例8.4】宏定义时引用已定义宏名的应用示例。 #define PI 3.1415926 #define ZJ 2*PI main() printf(“ZJ=%d“,ZJ); (8)对于程序中用双引号括起来的字符串内的内容,即使与宏名相同,也不进行置换。比如在上例中对于语句“printf(“ZJ=%d“,ZJ);”来说,其中第一个ZJ就不被替换。,8.1.2 带参的宏定义 1引例 为了说明带参宏定义,先看一个例子。 【例8.5】使用带参宏定义计算圆的周长和面积。 #d

9、efine PI 3.1415926536 #define CIRCUM(r) 2.0*PI*(r) #define AREA(r) PI*(r)*(r) main() double zj,x; scanf(“%lf“, ,在编译预处理时进行以下替换:用printf函数中宏名CIRCUM(x)的实参x替代宏定义CIRCUM(r)中的形参r,经预编译后CIRCUM(x)变成2.0*PI*(x)。同样,AREA(x)经替代后得到PI*(x)*(x)。上面printf函数语句经替换后为: printf(“circum=%11.8lf,area=%11.8lfn“, 2.0*3.1415926536*

10、(x),3.1415926536*(x)*(x); 程序运行结果为: 5(回车) circum=31.41592654,area=78.53981634,2带参宏定义的格式 从例8.5中可以归纳出带参宏定义的格式为: #define 宏名(形参表) 宏体 对带参的宏定义是这样展开置换的:在程序中如果有带实参的宏,则按#define命令行中指定的字符串从左到右进行置换。如果串中包含宏中的形参,则将程序语句中相应的实参代替形参,如果宏定义中的字符串中的字符不是参数字符,则保留。,对于带参宏定义,宏体及其各个形参应该用圆括号括起来,否则会造成不易察觉的错误。 如下面三个宏定义: (1)#define

11、 SQUARE(x) x*x (2)#define SQUARE(x) (x)*(x) (3)#define SQUARE(x) (x)*(x) 对语句:a=2.7/SQUARE(n+1); (1)将替换为a=2.7/n+1*n+1; (2)将替换为a=2.7/(n+1)*(n+1); (3)将替换为a=2.7/(n+1)*(n+1); 显然(1)、(2)都是不正确的。,3带参的宏定义同函数的比较 由于带参的宏定义与函数有很多相似之处,初学者往往容易混淆。它们之间的确有许多共同的特点: (1)两者均带有形式参数,调用时也进行实参和形参的结合。 (2)两者都可以作为程序模块应用于模块化程序设计中

12、。 但带参宏定义同函数的区别也是明显的,不同之处在于。 (1)时空效率不相同。进行宏调用(使用宏定义)时,要宏体去替换宏名,往往使程序体积膨胀,加大了系统的存储开销。但是它不像函数调用要进行参数传递、保存现场、返回等操作,故时间效率比函数高。对于简短的表达式,以及调用次数多、要求响应快的场合(如实时系统中),采用宏定义比采用函数合适。 (2)宏定义虽然可以带有参数,但宏调用过程不像函数那样要进行参数值的计算、传递及结果返回等操作。宏定义只是简单的字符替换,不进行计算操作。因而一些过程是不能用宏定义代替函数的,比如递归调用。,【例8.6】宏定义不能应用于递归调用。 #define FACT(n)

13、 (n)=1)?1:FACT(n)-1) main() int m=5; printf(“fact%d=%d“,m,FACT(m); 这是一个企图用宏实现递归调用来求5!的程序,但是它连编译也通不过,因为宏不能递归定义。 (3)函数中的实参和形参都要定义类型,且要求二者的类型一致,如不一致,则将进行类型转换。对于宏不存在类型问题,宏名无类型,它的参数也无类型。 (4)宏调用与函数调用相比,前者没有内部类型检查,因而,形参和实参数据类型之间的类型不匹配可能产生难以预测的结果,而且不立即发生警告。 (5)宏调用还可能产生函数调用所没有的副作用。,【例8.7】试比较下面打印整数14的平方的两个程序。

14、 采用函数的程序如下: int square(n) return(n*n); main() int i=1; while(i=4)printf(“%d,”,square(i+); 执行结果:1,4,9,16, 这一程序执行结果是成功的。采用宏的程序如下: define SQUARE(n) (n)*(n) main() int i=1; while(i=4) printf(“%d,”, SQUARE(i+); 在IBM PC计算机上用Turbo C编译后,执行结果如下: 2,12, 程序未达到预期目的。原因是在宏定义后printf函数语句被置换成: printf(“%dn“,(i+)*(i+);

15、,Turbo C在处理函数实参求解时采用自右而左逐项求解,而i+又是“先使用,后自加”,因此当i的初值为1时,在第一次循环中,先处理右边的(i+),先用i的原值1,然后i加1变成2,这时左边的(i+)的i值就是2,进行1*2的运算得2,然后i自加1变成3。同理,在第二次循环中,进行3*4的运算,然后i变成5,退出循环。 可以看到,使用带参的宏,引入了i+的副作用,而用函数则不会出现此问题。因为在函数中i+作为实参只出现一次,而在宏定义体中i+出现两次。 顺便说明一下,关于实现i+或+i运算以及C表达式求值次序问题,对一些具体细节初学者可能会感到不好掌握,例如上面的函数参数(i+)*(i+)求值问题。如果(i+)*(i+)不是作为函数参数而是作为赋值表达式的一部分,求值结果就不同了。,例如: i=1; j=(i+)*(i+); 此时j的值不是2,而是1。在此情况下的求值次序为:先将i的原值1取出来使用(在整个表达式中使用),进行1*1得到1,赋于j,然后再执行两次i自加操作,i变成3。关于i+这些副作用的细节不必深究,在此提一下只是为了使大家在遇到此类问题时不致感到茫然。实在弄不清时,上机试一下即可。,8.1.3 书写宏定义命令行应注意的问题 (1)宏名与宏体之间应以空格相隔,宏名中不能含有空格。如有宏定义: #define fun (x

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 高等教育 > 大学课件

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号