第十一章预处理命令内蒙古科技大学实训中心预处理命令概述所谓预处理,就是指源程序被正式编译之前所进行的处理工作,这是C语言和其他高级语言之间的一个重要区别。所有的预处理指令均以“#”开头,在它前面不能出现空格以外的字符,而且在行结尾处没有分号。“预处理命令”的作用不是实现程序的功能,它们是发布给编译系统的信息。它们告诉编译系统,在对源程序进行编译之前应该做些什么,所以称这类语句为编译预处理命令。C语言在执行一个C程序时,如果程序中有预处理命令,则首先进行编译预处理(即根据预处理命令对源程序进行预先处理),然后再将经过预处理的源程序编译成目标文件,而后进行目标文件的连接,当这些工作都顺利通过后,才最终执行目标代码。这种执行过程大大提高了编程效率。预处理命令C语言提供的预处理命令有三种:1、宏定义2、文件包含3、条件编译宏定义是用一个标识符来代表一个字符串,其中标识符称为宏名。在编译预处理时,将会把宏名替换成它所代表的字符串,这个过程称为宏展开。一、宏定义(一)无参数宏定义无参数宏定义的一般格式为:#define标识符字符串其中,标识符为用户自定义的合法的标识符,即宏名。例如:#definePI3.1415926PI是宏名,它代表字符串3.1415926。注意:不要认为PI是一个值为3.1415926的变量。宏定义与变量定义不同,宏定义只是在编译预处理时做简单的字符串替换,并不需要系统分配内存空间;而定义变量则会在编译时得到系统分配的内存空间。例1:利用宏定义求圆的周长和面积。#definePI3.1415926#defineR1.0main(){floatl,s;l=2.0*PI*R;s=PI*R*R;printf(“周长=%f,面积=%f\n″,l,s);}左面程序在编译前将进行宏展开,宏展开以后变为:main({floatl,s;l=2.0*3.1415926*1.0;s=3.1415926*1.0*1.0;printf(“周长=%f,面积=%f\n”,l,s);}几点说明:⑴宏名一般习惯用大写字母来表示,以便于与变量名相区分,但不是硬性规定,也可以用小写字母。同一个宏名不能重复定义。⑵宏定义是用宏名代替一个字符串,只做简单的替代,不做正确性检查。如果字符串书写错误,预处理时也照样进行替换。⑶用宏名来代替字符串,可以减少程序输入过程中重复输入的某些字符串。⑷宏定义时如果在行末有分号,则替换时连分号一起进行替换。例如:#defineX5;……s=X*8;……则经过宏展开后,该语句为:s=5;*8。显然出现语法错误。因此在宏定义时,行末不要加分号。⑸在宏定义时,如果一行写不下时,需要在上一行的结尾处加上“\”来表示续行。例如:以下语句与#definePI3.14159是等价的⑹宏定义时,可以引用已经定义的宏名。例如:#definePI\3.14159#defineX5#defineYX+1#defineZY*X/2若有执行语句:printf(“%d\n”,Z);则输出结果是7因为替换文本中Y*X/2相当于X+1*X/2,即为表达式5+1*5/2,结果为7。如果想要计算的是(X+1)*X/2,应该在宏定义时把X+1用括号括起来。例如:#defineY(X+1)⑺通常#define命令写在文件的开头,其作用域为整个源文件;否则从宏定义命令之后到本源文件结束为止。在预处理时,将只对第二个RADIUM进行置换,而对第一个双引号中的RADIUM系统并不对其做置换。⑻对于程序中写在双引号中的字符串,即使与宏名相同,也不对其进行置换。例如,如果程序中有以下语句:#defineRADIUM234……printf(RADIUM=,RADIUM);因此其输出结果为:RADIUM=234⑼可以使用#undef命令终止宏定义的作用域。例如:PI的有效范围#definePI3.14159main(){……}#undefPIvoidear(){……}PI的有效范围因为#undef的作用是终止宏定义,因此PI的作用域从它定义开始到#undef结束。在这以后,如果程序中出现PI,则它不代表3.14159。使用#undef可以灵活控制宏定义的作用范围。(二)带参数宏定义(了解)定义的一般格式为:#define宏名(形式参数列表)字符串其中,字符串应包含括号中所指定的形式参数。注意:宏名与括号之间不要有空格,否则就被认为是不带参数的宏定义。例:#defineS(a,b)a*barea=S(3,2);带参数的宏展开过程为:预编译时遇到带实参的宏名,则按命令行中指定的字符串从左到右进行置换。其展开原则是:遇形参则以实参代替,非形参字符原样保留,从而形成展开后的内容。带参数的宏定义,其的作用域及其定义、使用方法与不带参数的宏定义相同。不同的是,带参数的宏定义除了用字符串置换宏名之外,还要进行参数的置换,即在预处理展开时用实参代替形参。例如:#definePI3.14159#defineRadium(area)sqrt(area/PI)。如果在程序中有下面的语句:R1=Radium(100);那么,在编译预处理时,宏展开的顺序为从左到右进行置换,如果字符串中包含宏中的形参,则将其用程序语句中相应的实参来替代,而字符串中的其它字符原样保留。因此,在处理上述语句时,将用sqrt(area/PI)替代Radium(100),同时将字符串sqrt(area/PI)中的形参area用实参l00来替代,并把已定义的宏PI的值代入,经宏展开后,该语句变为“R1=sqrt(100/3.14159);”。#definePI3.1415926#defineV(r)4.0/3.0*PI*r*r*r/*定义带参数的宏V(r)*/main(){floatr,v;printf(Inputanumber:\n);scanf(%f,&r);v=V(r);printf(r=%f\nvolumn=%f\n,r,v);}例2:计算球体的体积。预编译时进行宏展开后,将用实参变量r代替宏定义中的形参r,使得计算体积的语句成为如下形式:v=4.0/3.0*3.1415926*r*r*r,程序运行时再带入变量r的值进行计算求得体积。#includestdio.h#defineMIN(x,y)(x)(y)?(x):(y)main(){inti,j,k;i=10;j=15;k=10*MIN(i,j);printf(k=%d\n,k);}例3:分析以下程序的运行结果。如果我们认为:MIN宏用于求两个数的较小值,立即得出k=10*10=100,所以输出为k=100,这样是错误的。应该是:宏展开后得到k=10*(10)(15)?(10):(15),而乘法运算符高于条件运算符,应该先算乘法得到k=10015?10:15。再作条件运算得到k=15,所以正确的输出结果应该是:k=15。如果我们想得到10与宏的最小值的乘积,则该程序应改为:宏展开后得到k=10*(1015?10:15)=10*10=100。所以输出为:k=100。#includestdio.h#defineMIN(x,y)((x)(y)?(x):(y))main(){inti,j,k;i=10;j=15;k=10*MIN(i,j);printf(k=%d\n,k);}如果能够合理利用宏定义,可以实现程序的简化,如将程序中的“输入或输出格式”事先定义好,可以减少程序中输入或输出语句每次都要写出具体格式的麻烦。#definePRIprintf#defineSCscanf#defineNL\n#defineD%f#defineD1DNL#defineD2DDNLmain(){floata,b=4.0,c=5.0;SC(D,&a);PRI(D1,a);PRI(D2,b,c);}例4:利用宏定义编写自己的输入输出格式。D代表一个实型数据的格式;D1代表输出完一个实型数据后换行;D2代表输出完两个实型数据后换行。我们还可以写出其它输入或输出的格式,把它们编成一个文件存放在磁盘上,当使用时用#include命令把它包括到自己所编的程序中,这样就可以根据自己的需要使用不同的格式了。文件包含的功能是把一个或多个指定文件嵌入到现行的源程序文件中,再对嵌入后的源程序文件进行编译处理。这样可以有效地减少重复编程。我们已经使用过的#includestdio.h、#includestring.h、#includemath.h等就是文件包含。例如:math.h头文件里定义了大量的数学函数的函数原型,如sqrt函数(开平方函数),在求一个数开平方时,如果不使用文件包含命令,在程序中就不能使用这个库函数。(一)文件包含命令的一般格式:#include文件名或者#include文件名二、文件包含其中,文件名是指磁盘中所要包含的文件的名字。例如:#includestdio.h#includefile1.cstdio.h是由系统提供的头文件。file1.c是用户提供的C程序文件,这样就把两个源程序文件组合成一个源程序文件了。文件包含中,用尖括号和双引号的区别在于指示编译系统使用不同的方式搜索包含文件。尖括号:编译系统将仅在系统设定的标准目录中搜索所包含的文件。例如:#includestring.h系统只在设定的标准目录include下查找包含文件string.h。双引号:(文件名中无路径时,)编译系统将首先在源文件所在的目录中查找,若未找到,再到系统设定的标准目录中查找。例如:#includeprog.h当双引号括住的被包含文件有指定路径时,编译系统将只按指定的路径去查找包含的文件。例如:#includec:\user\user.h编译系统将在c:\user子目录下查找被包含文件user.h。如果指定的文件不存在,编译系统将提示出错信息,并停止编译过程。几点说明:⑴一般来说,文件包含控制行可出现在源文件的任何地方,通常都放在文件开头。⑵一个#include只能指定—个包含文件。如果需要把多个文件包含到源文件之中,必须使用多个#include命令行。⑶当被包含文件修改时,凡包含此文件的所有源程序都必须重新进行编译。定义一个判断字符是大写字母的宏、一个判断字符是小写字母的宏以及实现大小写字母相互转换的宏,并将用户输入的一个字符串中的大小写字母互换。综合应用程序举例#includestdio.h#defineisupper(c)((c)='A'&&(c)='Z')#defineislower(c)((c)='a'&&(c)='z')#definetolower(c)(isupper(c)?((c)+('a'-'A')):(c))#definetoupper(c)(islower(c)?((c)-('a'-'A')):(c))main(){chars[20];inti;printf(Inputthestring:);scanf(%s,s);for(i=0;s[i];i++)if(isupper(s[i]))s[i]=tolower(s[i]);elseif(islower(s[i]))s[i]=toupper(s[i]);printf(Outputthestring:%s\n,s);}⑴C语言中所有的预处理命令都以字符“#”号开头,行尾不能加分号。⑵所有的预处理命令都在编译前处理,因此它不具有任何计算、操作等功能。⑶预处理命令若有变动,必须对程序重新进行编译和连接。⑷在写带有参数的宏定义时,宏名与带括号参数间不能有空格,否则将空格以后的字符都作为替换字符串的一部分,这样就成了不带参数的宏定义了。例如:#defineS(x)PI*x*x则定义的S为不带参数的宏名,它代表字符串“(x)PI*x*x”。⑸不要把带参数的宏定义与带参数的函数搞混。带参数的宏定义在预处理时只是字符串的替换,而不是像带参数的函数那样有值的传递。总结