第9章编译预处理_第1页
第9章编译预处理_第2页
第9章编译预处理_第3页
第9章编译预处理_第4页
第9章编译预处理_第5页
已阅读5页,还剩51页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第9章编译预处理9.1宏定义9.2文件包含9.3条件编译习题99.1宏定义

9.1.1无参数的宏定义 无参数宏的宏名后不带参数。其定义的一般格式为 #define宏名字符串其中的“#”表示这是一条预处理命令。凡是以“#”开头的均为预处理命令。“define”为宏定义命令。“宏名”为所定义的宏名。“字符串”可以是常数、表达式和格式串等。在前面介绍过的符号常量的定义就是一种无参宏定义。此外,常对程序中反复使用的表达式进行宏定义。 1.无参数宏定义符号常量 符号常量用无参数的宏定义语句定义,把符号常量名定义为指定的字符串,将程序中出现宏名的地方均用该字符串来替换。在进行编译预处理时,用该字符串替代程序中出现的符号常量名。例如:#defineTRUE1#defineFALSE0 把TRUE定义为1,把FALSE定义为0。在符号常量定义之后,就可以用它来编码了。 例如: if(i==TRUE) printf("youareright!\n"); elseif(i==FALSE) printf("youarewrong!\n"); 对于该程序段,在进行编译预处理时,就把程序中出现的TRUE和FALSE分别用1和0替代,于是就变为if(i==1)printf("youareright!\n");elseif(i==0)printf("youarewrong!\n"); 在符号常量定义语句中,字符串可以是一个数值型数据、表达式或字符串。例如:#definePI3.1415926#defineS(PI*r*r)#definePRTprintf#defineA(20-(3*4)) 如果字符串是一个运算表达式,一般应该用括号括住它,以便把它视为一个操作对象与其他操作数进行运算,否则,会由于操作优先级问题而发生错误。例如: text=A*8; 进行编译预处理后,该表达式变为 text=(20-(3*4))*8; 如果A定义为 #defineA20-(3*4) 则表达式text=A*8经预编译后变为 text=20-(3*4)*8; 这就不符合原意。因此,在宏定义语句中的字符串为一般表达式(而不是一个操作数)时,为了保证正确的运算次序,应该用括号括住它。因此在宏定义时必须十分注意,应保证在宏代换之后不发生错误。 2.无参数宏的好处 在程序设计中,使用无参数的宏有下面两点好处: 1)增强程序的可读性 以符号常量为例,由于符号常量含义明确,于是采用符号常量书写的程序要比不采用符号常量的可读性强。例如:#defineLENGTH20#defineWIDTH40#defineHEIGTH60 在程序中用LENGTH、WIDTH、HEIGTH时,一看就知道它们分别代表长、宽、高,而如果直接用20、40、60,则很难猜出它们是长、宽、高。 2)增强程序的可维护性 如果一个常量在程序中多次被引用,则可把它定义为符号常量。这样,在以后需改动该常量时,只需改动它的宏定义语句即可,而不必对每一个引用它的地方进行修改。这不但可以减少修改的工作量,而且可以避免漏改。 3.无参数宏的注意事项 使用无参数宏定义符号常量时,一般应注意以下几点。 (1)宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。如有错误,则只能在编译已被宏展开后的源程序时发现。 (2)符号常量名一般用大写字母(也可以用小写字母)表示,以便与其他标识符相区别。符号常量名的命名规则与一般标识符相同。另外,应考虑在字符串中根据需要加上括号。 (3)宏定义不是说明或语句,因此,不能用分号结尾。如果加上分号,则分号被作为字符串的一部分,连分号也一起置换。例如: #defineA60; 上面的格式表示A被定义为“60;”,而不是“60”。于是,在预编译时,程序中凡是出现A的地方,都用“60;”替换。这就不符合原意了。 (4)替换字符串可以为空。 (5)宏定义语句应放在函数定义之外,符号常量的有效范围是从定义它的宏定义语句开始至所在源文件的结尾。一般宏定义语句都放在源文件的开头,以便使它对整个源文件都有效。 (6)为了灵活控制宏定义的作用范围,可用“﹟undef”命令终止宏定义的作用域。 例如:#definePI3.14159main(){

}#undefPI /*PI的宏定义结束*/f1(){

} 表示PI只在main函数中有效,在f1中无效。 (7)宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层代换。例如:#definePI3.14#defineR10#defineSPI*R*Rmain(){printf("S=%f",S);}预编译后,该程序变为main(){printf("S=%f",3.14*10*10);} (8)宏名在源程序中若用引号括起来,则预处理程序不对其作宏代换。例如:#defineNO220main(){printf("NO");printf("\n");} 上例中定义宏名NO表示220,但在printf语句中NO被引号括起来,因此不作宏代换。程序的运行结果为 NO 表示把“NO”当字符串处理。 【例9-1】假设血压正常值低压为70,高压为120。如果低压高于70,并且高压低于120,则为正常。从键盘输入血压值,判断该血压值是否正常。#include<stdio.h>main()#defineLOW70 /*定义宏*/#defineHIGH120 /*定义宏*/{intbloodplow,bloodphigh;doscanf("%d%d",&bloodphigh,&bloodplow);while(bloodplow>=bloodphigh);if(bloodplow>LOW&bloodphigh<HIGH)printf("youareright!");elseprintf("youmayhavesomethingwrong!");}输入:13080输出:youmayhavesomethingwrong! 【例9-2】已知一梯形的上下两边的长分别为a、b,输入高h,求其面积。 #include<stdio.h>main()#definea5 /*定义宏*/#defineb15 /*定义宏*/#defineL(a+b) /*嵌套定义宏*/{floath,s;scanf("%f",&h);s=h*L/2;printf("s=%f\n",s);}输入:3输出:s=30.000000 【例9-3】利用迭代法求方程的根。其迭代公式为yi+1=(yi+x/yi)/2。#include"stdio.h"main()#defineABS1e-4{floaty1,y,x;printf("x=");scanf("%f",&x);printf("y=");scanf("%f",&y);do{y1=y;y=(y1+x/y1)/2;}while((y1-y)>ABS);printf("%f\n",y);}运行结果:x=2y=31.414214 9.1.2带参数的宏定义 1.带参数的宏的定义 利用#define语句不仅可以定义符号常量,也可以定义带参数的宏。带参数的宏的一般定义格式为 #define宏名(参数表)字符串 字符串中包含参数表中的参数。 调用带参数宏的一般格式为 宏名(实参表); 例如: #defineMIN(a,b)(((a)<(b))?(a):(b)) 其中,MIN(a,b)是带参数的宏,a和b是形式参数。该定义把MIN(a,b)定义为“(((a)<(b))?(a):(b))”。在定义了该宏后,就可在程序中用MIN(a,b)替代定义它的运算表达式“(((a)<(b))?(a):(b))”。宏的使用方法类似函数。例如,在需要求两个数的最小值时,就可以使用已定义的宏。 c=MIN(10,20); 在进行编译时,预编译程序根据宏定义式来替换程序中出现的带参数的宏,其中定义式中的形式参数用相应的实际参数替换。于是,上面的赋值语句变为 c=MIN((10<20)?10:20); 在程序设计中,经常要把反复使用的运算表达式定义为带参数的宏。例如: #definePER(a,b)(100.0*(a)/(b)) /*求a是b的百分之几*/ #defineABS(x)((x)>=0)?(x):-(x) /*求x的绝对值*/ #defineMAX(a,b)(((a)<(b))?(a):(b)) /*求两个数中的较大数*/ #defineISO(x)(((x)%2==1)?1:0) /*判断是否为奇数*/ 2.带参数的宏的好处 使用带参数的宏有下面两点好处: (1)使程序更加简洁,减少不必要的重复书写。 (2)增强程序的可读性,一般用一个含义明确的宏名代替一个较复杂的运算式,会使读者一目了然。 3.带参数的宏与函数的区别 从上面的介绍中可以看出,带参数的宏和函数在使用形式和特性上都很相似。但是,二者又有本质区别,主要表现在以下几个方面。 (1)函数调用时,要保留现场和返回点,而后把控制转移给被调用函数。当被调用函数执行结束后,又要恢复现场和把控制返回到调用函数。而对带参数宏的使用不存在控制的来回转移,它只是表达式的运算。 (2)函数有一定的数据类型,且数据类型是不变的。而带参数的宏一般是一个运算表达式,它没有固定的数据类型,其数据类型就是表达式运算结果的数据类型。同一个带参数的宏,随着使用实参类型的不同,其运算结果的类型也不同。 (3)函数定义和调用中使用的形参和实参都受数据类型的限制,而带参数宏的形参和实参可以是任意的数据类型。 (4)函数调用中存在参数的传递过程,而带参数宏的引用不存在参数传递过程。在函数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行“值传递”。而在带参宏中,只是符号代换,不存在值传递的问题。在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。例如:#defineSQ(y)(y)*(y)main(){inta,sq;printf("inputanumber:");scanf("%d",&a);sq=SQ(a+1);printf("sq=%d\n",sq);} 上例中第1行为宏定义,形参为y。程序第7行宏调用中实参为a+1,是一个表达式,在宏展开时,用a+1代换y,再用(y)*(y)代换SQ,得到如下语句: sq=(a+1)*(a+1); 这与函数的调用是不同的,函数调用时要把实参表达式的值求出来再赋予形参。而宏代换中对实参表达式不作计算直接地照原样代换。 (5)使用函数可缩短程序占用的内存空间,但由于控制的来回转移,会使程序的执行效率降低。而带参数的宏则相反,多次使用宏会增加程序占用的存储空间,但其执行效率要比函数高。 除了使用运算表达式来定义带参数的宏外,还可使用函数来定义它,标准函数库中经常采用这种方式。例如:#definegetchar()fgetc(stdin)#defineputchar(ch)fputc(ch,stdout) (6)宏定义也可用来定义多个语句,在宏调用时,把这些语句又代换到源程序内。看下面的例子:#defineSSSV(s1,s2,s3,v)s1=l*w;s2=l*h;s3=w*h;v=w*l*h;main(){intl=3,w=4,h=5,sa,sb,sc,vv;SSSV(sa,sb,sc,vv);printf("sa=%d\nsb=%d\nsc=%d\nvv=%d\n",sa,sb,sc,vv);} 程序第1行为宏定义,用宏名SSSV表示4个赋值语句,4个形参分别为4个赋值符左部的变量。在宏调用时,把4个语句展开并用实参代替形参。使计算结果送入实参之中。 4.带参数的宏的注意事项 为了正确定义和使用带参数的宏,编程时应注意以下几点。 (1)对宏定义语句中的定义式和形式参数,要根据需要加上圆括号,以免发生运算错误。例如:#defineX(a)((a)*(a)*(a))

a1=1;a2=2;x=X(a1+a2); 经过预编译后,该赋值语句变为 x=((a1+a2)*(a1+a2)*(a1+a2)); 如果定义式中不使用相应的括号,则预编译后的赋值语句变为 x=a1+a2*a1+a2*a1+a2; 这样就与原来的意思不符合了。所以在定义带参数的宏时,一定要注意加上相应的括号。 (2)在定义带参数的宏时,在宏名和带参数的圆括号间不能有空格,否则,空格之后的字符串都将被视为替代字符串。例如:若把 #defineMAX(a,b)(a>b)?a:b 改写为#defineMAX(a,b)(a>b)?a:b 将被认为是无参宏定义,宏名MAX代表字符串“(a,b)(a>b)?a:b”。宏展开时,宏调用语句: max=MAX(x,y); 将变为max=(a,b)(a>b)?a:b(x,y); 这显然是错误的。 【例9-4】键盘输入立方体的边长a,求其表面积s及体积v。#include<stdio.h>main()#defineL(a,s,v)s=6*a*a;v=a*a*a{inta1,s1,v1;a1=0;scanf("%d",&a1);L(a1,s1,v1);printf("%d,%d,%d\n",a1,s1,v1);} 【例9-5】求1~100所有奇数的和。#include<stdio.h>main()#defineISODD(x)(((x)%2==1)?1:0){intsum,i;sum=1;for(i=3;i<=100;i++)ifISODD(i)sum+=i;printf("%d\n",sum);} 【例9-6】已知某单位上缴个人所得税的算法如下: 输入工资,求其应上缴的税款。#include<stdio.h>main()#defineTAX1(a)((a<=800)?0:((a<1300)?0.05:((a<=2800)?0.1:((a<=5800)?0.15:0.2))))#defineTAX2(b)((b<=1300)?0:((b<=2800)?25:((b<=5800)?125:375)))#defineTAX3(c)c*TAX1(c)-TAX2(c){floattax,wage;scanf("%f",&wage);tax=TAX3(wage);printf("%f,%f\n",wage,tax);} 【例9-7】输入4个整数,按由小到大的顺序输出。#include<stdio.h>main()#defineCHANGE(a,b){intt;t=a;a=b;b=t;}{inti,j,k,l;printf("pleaseinputfournumber:");scanf("%d,%d,%d,%d",&i,&j,&k,&l);if(i>j)CHANGE(i,j);if(i>k)CHANGE(i,k);if(i>l)CHANGE(i,l);if(j>k)CHANGE(j,k);if(j>l)CHANGE(j,l);if(k>l)CHANGE(k,l);printf("theproperorderis:");printf("%d%d%d%d\n",i,j,k,l);}9.2文件包含 在前面章节中,我们经常在编写程序中,会写上下面的语句: #include<stdio.h> 其含义是在编译时,用stdio.h头文件的内容替换该语句。 文件包含语句的一般格式为 #include"文件名" 或#include<文件名> 其中,<文件名>是被包含文件的文件名,它是一个磁盘文件。该预编译语句的功能是要将 <文件名>所指文件的全部内容包含在该#include语句所在的源文件中。也就是说,在预编译时,用<文件名>所指文件的全部内容替换该#include语句行,使该文件成为这个源文件的一部分。 在#include语句的书写格式中,被包含文件的文件名可用尖括号(<>)括住,也可以用双引号("")括住。 当用尖括号括住时,表示编译系统按系统设定的标准目录搜索文件;当用双引号括住时,表示按指定的路径搜索。若未指定路径名时,则在当前目录中搜索。 文件包含语句是很有用的语句,特别是对包括多个源文件的大程序来说,可以把各个源文件中共同使用的函数说明、符号常量定义、外部量说明、宏定义和结构类型定义等写成一个独立的包含文件,在需要这些说明的源文件中,只需在源文件的开头用一个#include语句把该文件包括进来,这样就可以避免重复工作。例如:/*file1.c*/#include"file2.h"main(){

}/*file2.h*/#definePI3.14159 做成包含文件的另一个好处是,当这些常量、宏定义等需要修改时,只需修改这个被包含的文件即可,而不必修改各源文件。 使用#include语句时,应注意以下两点: (1)一个include命令只能指定一个被包含文件,若有多个文件要包含,则需用多个include命令。 (2)文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。9.3条件编译 一般情况下,源程序中所有的行都参加编译,但有时在写程序时要求根据具体情况编译不同的程序代码,C语言中提供了条件编译,可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。这对于程序的移植和调试是很有用的。条件编译有3种形式,下面分别介绍。 1.#ifdef…#else…#endif语句 用#ifdef…#else…#endif语句进行条件编译的指令格式为 #ifdef标识符程序段1[#else程序段2]#endif 其作用是:如果标识符已被定义(用#define定义),则对程序段1进行编译,而程序段2被删除;否则,程序段1被删除,编译程序段2。其中,#else部分是可以缺省的,即#ifdef标识符程序段1#endif 条件编译语句中的#ifdef和#endif决定了编译范围,在此范围外的源程序不存在条件编译问题。条件编译对于提高程序的移植性很有帮助。

【例9-8】条件编译#ifdef的使用。#include"stdio.h"#defineTED10main(){#ifdefTEDprintf("HiTed\n"); /*如果定义了TED,则编译此行代码*/#elseprintf("Hianyone\n"); /*如果没用定义TED,则编译此行代码*/printf("Hianyone\n"); /*如果没用定义TED,则编译此行代码*/#endif#ifndefRALPHprintf("RALPHnotdefined\n"); /*如果定义了RALPH,则编译此行代码*/#endif} 上述代码打印“HiTed”及“RALPHnotdefined”。如果TED没有定义,则显示“Hianyone”,后面是“RALPHnotdefined”。可以像嵌套#if那样将#ifdef与#ifndef嵌套至任意深度。 2.#ifndef…#else…#endif语句 由#ifndef…#else…#endif语句进行条件编译的指令格式为#ifndef标识符程序段1[#else程序段2]#endif 与第一种形式的区别是将“ifdef”改为“ifndef”。它的功能是:如果标识符未被#define命令定义,则对程序段1进行编译,否则对程序段2进行编译。这与第一种形式的功能正相反。例如:#ifndefUNPRNprintf("Name=%ssa=%f",name,s);#elseprintf("%s%f",name,s);#endif 当UNPRN在程序段之前未定义时,则只编译“printf("Name=%ssa=%f",name,s);”。如果在该程序段之前加一行: #defineUNPRN1 则只编译“printf("%s%f",name,s);”。其中,UNPRN可定义为任何字符串。 3.#if…#else…#endif语句 由#if…#else…#endif语句进行条件编译的指令格式为#if表达式程序段1[#else程序段2]#endif 其作用是:当表达式的值为非0时,编译程序段1,不编译程序段2;否则编译程序段2(其中#else部分是可以缺省的)。例如,在程序设计的测试阶段,经常要显示一些变量的信息,以检查是否正确,而在正式执行时,却不需要显示这些信息。这时,就可以用下面形式的条件编译。#defineDEBUG1#ifDEBUGprintf("a=%db=%fc=%s",a,b,c);#endif 以上形式的条件编译适用测试阶段,如果程序测试完成,在编译正式的执行代码时,只需把DEBUG定义为0即可。在进行条件编译时,可根据情况选择条件编译语句。 【例9-9】条件编译#if的使用。#include"stdio.h"#defineR1main(){floatc,r,s;printf("inputanumber:");scanf("%f",&c);#ifR r=3.14159*c*c; /*如果R非0,则编译此行和下一行代码

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论