教学内容:9.1宏处理9.2文件包含9.3条件编译教学要求:1.理解预处理功能在程序设计中的作用。2.掌握宏定义的使用。3.掌握文件包含、条件编译的应用。第9章预处理编译预处理编译预处理是指,在对源程序进行编译之前,系统将自动引用预处理程序对源程序中的预处理部分作处理;然后再将处理的结果,和源程序一起进行编译,以得到目标代码。宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。在C语言中,“宏”分为有参数和无参数两种。使用宏定义的优点(1)可提高源程序的可维护性,可移植性(2)减少源程序中重复书写字符串的工作量1.不带参数的宏定义(1)无参宏定义的一般格式#define标识符语言符号字符串9.1宏处理define:为宏定义关键字;标识符:定义的宏名,通常用大写字母取名,以便于与变量区别;语言符号字符串:即宏体,可以是常数、表达式、格式串等。例如:#definePI3.1415926定义了一个符号常量PI,表示用标识符PI替换3.1415926。在编译预处理时,会将程序代码中所有的PI都用3.1415926替换。2.关于不带参数的宏定义的几点说明:(1)宏名一般用大写字母表示,便于与变量名区别;一般将字符个数较多的字符串用一个宏名替换,减少程序中多处引用字符串书写错误。(2)宏定义是用宏名来表示一个字符串,在宏展开时以该字符串取代宏名,这只是一种简单的代换,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。(3)宏定义不是语句,在行末不加分号,如加上分号则连分号也一起置换。(4)宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令。例如:#definePI3.14159main(){……}#undefPI/*终止宏定义PI*/min(){....}表示PI只在main函数中有效,在min中无效。(5)宏名在源程序中若用引号括起来,则预处理程序不对其作宏代换。(6)宏定义可以嵌套,但嵌套的宏定义名要是已经定义的宏名。在宏展开时由预处理程序层层替换。例如:#defineR14.256#definePI3.1415926#defineSPI*R*R/*PI、R是已定义的宏名*/则语句printf(%f,s);在宏代换后变为printf(%f,3.1415926*14.256*14.256);(7)可用宏定义表示数据类型,使书写方便。例如:#defineSTUint在程序中可用STU替换数据类型int。例9.1不带参数的宏的程序#includestdio.h#definePI3.115926#defineSTRINGThisisatestmain(){floatr,s;printf(STRING\n);scanf(%f,&r);while(r0){s=PI*r*r;printf(s=%10.3f\n,s);scanf(%f,&r);}}宏定义不是C语句,所以不能在行尾加分号。否则,宏展开时,会将分号作为宏体中的1个字符。在宏展开时,预处理程序仅以宏体替换宏名,而不作任何检查例9.2在进行宏定义时,可以引用已定义的宏名。#definePI3.1415926#defineLPI*r*2#defineARPI*r*r#defineVOAR*r*3/4main(){floatr;printf(Inputaradius:);scanf(%f,&r);printf(length=%.2f,area=%.2f,L,AR);printf(vo=%.2f\n,VO);}在进行宏定义时,可以引用已定义的宏名,层层置换例9.3#defineN2#defineMN+1#defineNUM2M+1main(){inti;for(i=1;i=NUM;i++)printf(“%d\n”,i);}宏展开后的等价程序如下:main(){inti;/**/for(i=1;i=6;i++)printf(“%d\n”,i);}NUM宏展开:2*N+1+1再宏展开:2*2+1+13.带参宏定义(1)带参宏定义格式:#define宏名(形参表)宏体(2)带参宏调用格式:宏名(实参表)(3)宏展开:用宏调用提供的实参字符串,直接置换宏定义命令行中相应形参字符串,非形参字符保持不变带参宏定义说明(1)定义有参宏时,宏名与左圆括号之间不能留有空格。否则,C编译系统将空格以后的所有字符均作为替代字符串,而将该宏视为无参宏。(2)有参宏的展开,只是将实参作为字符串,简单地置换形参字符串,而不做任何语法检查。在定义有参宏时,在所有形参外和整个字符串外,均加一对圆括号。例9.4带参数的宏#defineDS(a)1.0/amain(){floatr1,r2,r;r1=5.5;r2=50.8;r=DS(r1)+DS(r2);printf(“R=%f\n”,1.0/r);}运行结果:R=4.962700形参:a实参:r1,r2例9.5带参数的宏#includestdio.h#defineF(a)a*a/*宏名F,形参a*/main(){intx=3,y=4,b,z;b=x+y;z=F(x+y);/*展开为z=x+y*x+y;*/printf(b=%d\nz=%d\n,b,z);}4.带参的宏和带参函数区别(1)在函数调用时,是先求出实参表达式的值,再传递给形参,而宏定义只是简单的字符替换;(2)函数调用是在程序运行时处理的,分配存储单元,而宏展开(调用)是在编译预处理时进行的,展开时不分配内存单元,不进行值传递,没有返回值的概念;(3)对函数实参和形参都要定义类型,而宏不存在类型,宏定义时字符串可以是任何类型数据,一律看成字符串,宏名也没类型,只是一个符号表示,展开时代入指定的符号即可。(4)定义带参数的宏,可以实现一些简单的函数功能。#defineMAX(x,y)(x)(y)?(x):(y)main(){inta,b,c,t;t=MAX(a+b,c+d);/*展开后为t=(a+b)(c+d)?(a+b):(c+d)*/}如果第一行写成:#defineMAX(x,y)xy?x:y则展开后为t=a+bc+d?a+b:c+d;因为置换展开是用“表达式”对等的置换“形参表”中的参数。例9.6带参数的宏的宏体中有()#defineSA(i)i*i#defineSB(j)(j)*(j)main(){inta,b,x,y;a=3;b=7;x=SA(a+b)/SA(a+b);/*x=a+b*a+b/a+b*a+b*/y=SB(a+5)/SB(b+2);/*y=(a+5)*(a+5)/(b+2)*(b+2)*/printf(“x=%d,y=%d\n”,x,y);}运行结果:x=54,y=639.2文件包含1.文件包含的概念文件包含是指,一个源文件可以将另一个源文件的全部内容包含进来。2.文件包含处理命令的格式#include“包含文件名”或#include包含文件名两种格式的区别仅在于:(1)使用双引号:系统首先到当前目录下查找被包含文件,如果没找到,再到系统指定的“包含文件目录”(由用户在配置环境时设置)去查找。(2)使用尖括号:直接到系统指定的“包含文件目录”去查找。一般地说,使用双引号比较保险。3.文件包含的优点一个大程序,通常分为多个模块,并由多个程序员分别编程。有了文件包含处理功能,就可以将多个模块共用的数据(如符号常量和数据结构)或函数,集中到一个单独的文件中。这样,凡是要使用其中数据或调用其中函数的程序员,只要使用文件包含处理功能,将所需文件包含进来即可,不必再重复定义它们,从而减少重复劳动。4.说明(1)编译预处理时,预处理程序将查找指定的被包含文件,并将其复制到#include命令出现的位置上。(2)常用在文件头部的被包含文件,称为“标题文件”或“头部文件”,常以“h”(head)作为后缀,简称头文件。在头文件中,除可包含宏定义外,还可包含外部变量定义、结构类型定义等。(3)一条包含命令,只能指定一个被包含文件。如果要包含n个文件,则要用n条包含命令。(4)文件包含可以嵌套,即被包含文件中又包含另一个文件。Atypefile2.c/*显示file2.c内容*/fun2(){printf(“file2include\n”);}Atypefile1.c#include“file2.c”/*包含file2.c文件*/fun1(){printf(“file1include\n”);fun2();}Atypeexpfile.c#include“file1.c”/*包含file1.c文件*/main(){printf(“expfileinclude\n”);fun1();printf(“end\n”);}9.3条件编译条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程序的效率。条件编译可有效地提高程序的可移植性,并广泛地应用在商业软件中,为一个程序提供各种不同的版本。(1)在不同的系统中,一个int型数据占用的内存字节数可能是不同的。(2)利用条件编译,还可使同一源程序即适合于调试(进行程序跟踪、打印较多的状态或错误信息),又适合高效执行要求。1.第一种形式#ifdef标识符程序段1[#else程序段2#endif]如果标识符(宏定义的标识符)已被#define命令定义过则对程序段1进行编译;否则对程序段2进行编译。如果没有程序段2,本格式中的#else可以没有,即可以写为:#ifdef标识符程序段#endif例9.7条件编译实例1#includestdio.h#defineTED10main(){#ifdefTEDprintf(Hi,Ted\n);#elseprintf(Hi,Anyone\n);#endif#ifndefRALPHprintf(RAPLHnotdefined\n);#endif}2.第二种形式#ifndef标识符程序段1#else程序段2#endif与第一种形式的区别是将“ifdef”改为“ifndef”。它的功能是,如果标识符未被#define命令定义过则对程序段1进行编译,否则对程序段2进行编译。这与第一种形式的功能正相反。3.第三种形式#if常量表达式程序段1#else程序段2#endif它的功能是,如常量表达式的值为真(非0),则对程序段1进行编译,否则对程序段2进行编译。可以使程序在不同条件下,完成不同的功能。例9.8条件编译实例2#includestdio.h#defineMAX10main(){#ifMAX99printf(compileforarraygreaterthan99);#elseprintf(compileforsmallarray);#endif}例9.9条件编译实例3#defineLETTER1main(){charstr[20]=“Clabguage”,c;inti=0;while((c=str[i]!=‘\0’){i++;#ifLETTERif(c=‘a’&&c=‘z’)c=c–32;#elseif(c=‘A’&&c=‘Z’)c=c+32;#endifprintf(“%c”,c);}}运行结果:CLANGUAGE同学们再见