您好,欢迎访问三七文档
当前位置:首页 > 临时分类 > 08 编译预处理(最终讲稿)
第8章编译预处理河南理工大学重点、难点本章重点:宏定义命令条件编译命令文件包含命令本章难点:带参宏定义条件编译内容提要8.1宏定义8.1.1无参宏定义8.1.2带参宏定义8.2条件编译8.3文件包含所谓编译预处理是指,在对源程序进行编译之前,先对源程序中的编译预处理命令进行处理;然后再将处理的结果和源程序一起进行编译,得到目标代码。C语言提供的编译预处理命令主要有宏定义、条件编译和文件包含等三种。为了能够和一般C语句区别开来,编译预处理命令以“#”号开头。它占用一个单独的书写行,命令行末尾没有分号。8.1宏定义宏定义是指将一个标识符(又称宏名)定义为一个字符串(或称替换文本)。在编译预处理时,对程序中出现的所有宏名都用相应的替换文本去替换,这称为“宏替换”或“宏展开”。在C语言中,“宏定义”可分为无参宏定义和带参宏定义两种。8.1.1无参宏定义无参宏定义即定义没有参数的“宏”,其一般形式为:#define标识符替换文本其中#define表示该语句行是宏定义命令,“标识符”为所定义的宏名,习惯上宏名用大写字母表示;“替换文本”可以是常量、关键字、表达式、语句等任意字符串。在define、宏名和替换文本之间分别用空格隔开。#define命令可以不包含“替换文本”,此时仅说明宏名已被定义,以后可以使用。第2章介绍的符号常量的定义就是一种无参宏定义。例8-1用无参宏定义计算s=3*(y*y+3*y)+4*(y*y+3*y)+5*(y*y+3*y)#defineM(y*y+3*y)main(){ints,y;printf(Pleaseinputanumber:);scanf(%d,&y);s=3*M+4*M+5*M;printf(s=%d\n,s);}运行情况如下:Pleaseinputanumber:4↙s=336注意在宏定义中替换文本(y*y+3*y)两边的括号不能少,否则会产生错误。如改为以下定义:#difineMy*y+3*y则在宏展开时将得到下述语句:s=3*y*y+3*y+4*y*y+3*y+5*y*y+3*y;显然与原题意要求不符,计算结果当然是错的。因此在进行宏定义时必须注意,应保证宏代换之后不发生错误。说明(1)习惯上宏名用大写字母表示,以便与变量名区别开。但这并非规定,也允许使用小写字母。(2)用替换文本替换宏名只是一种简单的直接替换,替换文本中可以包含任意字符,系统在进行编译预处理时对它不作任何检查。例如:#definePI3.l415926(编译时才报错)(3)宏定义不是声明或执行语句,在行末不要加分号,如果加上分号则连分号也一起替换。(4)一个#define只能定义—个宏,且一行只能定义一个宏。若需要定义多个宏就要使用多个#define,并写在多行上。(5)宏定义时如果一行写不下,可用“\”续行。例如:#definePI3.1415926/*正确*/#definePI3.1415\926/*正确*/(6)宏定义原则上可以出现在源程序的任何地方,但通常写在函数之外,其作用域为从宏定义命令起到源程序文件结束。如要终止其作用域可使用#undef命令,其用法为:#undef标识符如:#undefPI(7)宏名在源程序中若用双撇号括起来,则在编译预处理时不对其作宏替换。也就是说,宏名被双撇号括起来时,仅作为一般字符串使用。例8-2宏替换的选择性。#definePI3.1415926main(){printf(PIis%9.7f.\n,PI);}程序运行结果为:PIis3.1415926.(8)宏定义允许嵌套,在宏定义的替换文本中可以使用已经定义过的宏名。在宏展开时层层替换。例如:#definePI3.1415926#defineSPI*r*r/*PI是已定义的宏名*/对语句:printf(%f,S);在宏替换后变为:printf(%f,3.1415926*r*r);例8-3用无参宏定义表示常用的数据类型和输出格式。#defineINTEGERint#defineREALfloat#definePprintf#defineD%d\n#defineF%f\nmain(){INTEGERa=5,c=8,e=11;REALb=3.8,d=9.7,f=21.08;P(DF,a,b);P(DF,c,d);P(DF,e,f);}程序运行结果为:53.80000089.7000001121.0800008.1.2带参宏定义带参宏定义的一般形式为:#define宏名(形参表)替换文本其中,形参表由一个或多个形参组成,各形参之间用逗号隔开,替换文本中通常应包括有形参。引用带参宏的一般形式为:宏名(实参表)带参宏定义展开时,先把宏引用替换为替换文本,再将替换文本中出现的形参用实参代替。例8-4用带参宏定义求两数中的大者#defineMAX(a,b)(ab)?a:bmain(){intx,y,max;printf(inputtwonumbers(x,y):);scanf(%d,%d,&x,&y);max=MAX(x,y);printf(max=%d\n,max);}程序运行情况如下:inputtwonumbers(x,y):5,6↙max=6注意!!!(1)在带参宏定义中,宏名与其后的左括弧“(”之间不得有空格,否则将变为无参宏定义。例如把:#defineMAX(a,b)(ab)?a:b写为:#defineMAX(a,b)(ab)?a:b将被认为是无参宏定义,宏名是MAX,替换文本为(a,b)(ab)?a:b。对语句:max=MAX(x,y);进行宏替换后,将变为:max=(a,b)(ab)?a:b(x,y);这显然是错误的。(2)在带参宏定义中,替换文本中的形参通常要用括号括起来以避免出错。例8-5分别引用以下宏定义,求3*F(3+2)的值。(A)#defineF(x)x*x+x(B)#defineF(x)(x)*(x)+(x)(C)#defineF(x)(x*x+x)(D)#defineF(x)((x)*(x)+(x))解:表达式3*F(3+2)在分别引用以上4个宏定义后,其值为:(A)因为宏定义只作为一种简单的字符替换,所以在引用(A)中的宏定义后,表达式3*F(3+2)被替换3*3+2*3+2+3+2(B)80。表达式3*F(3+2)被替换为:3*(3+2)*(3+2)+(3+2)(C)48。表达式3*F(3+2)被替换为:3*(3+2*3+2+3+2)(D)90。表达式3*F(3+2)被替换为3*((3+2)*(3+2)+(3+2))由此可见,使用带参数的宏定义,替换文本中的括号位置不同,可以得出不同的结果。使用时一定要仔细考虑。(3)宏定义也可用来定义多个语句,在宏替换时,把这些语句都替换到源程序中。例8-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);}程序运行结果为:sa=12sb=15sc=20vv=60带参宏定义与函数的主要区别如下:(1)定义方式不同。带参宏使用预处理命令#define定义;而函数使用函数定义。(2)参数性质不同。带参宏的参数表中的参数不必说明其类型,也不分配存储空间;而函数参数表中的参数需说明其类型并在使用时为其分配存储空间。(3)实现方式不同。宏展开是在编译时由预处理程序完成的,不占用运行时间;而函数调用是在程序运行时进行,需占用一定的运行时间。(4)参数传递不同。若实参为表达式,引用带参宏时只进行简单的字符替换,不计算实参表达式的值;而函数调用时,则先计算表达式的值,然后代入形参。(5)返回值不同。带参宏定义无返回值;而函数有返回值。例8-7带参宏定义的实参是表达式的情况#defineSQ(y)(y)*(y)main(){inta,sq;printf(inputanumber:);scanf(%d,&a);sq=SQ(a+1);printf(sq=%d\n,sq);}程序运行情况如下:inputanumber:3↙sq=16“完全展开,直接代替”例8-8函数与带参宏定义的进一步比较#defineSQ_MACRO(y)((y)*(y))main(){inti=1;printf(SQ_fun:\n);while(i=5)printf(%d\n,SQ_fun(i++));i=1;printf(SQ_MACRO:\n);while(i=5)printf(%d\n,SQ_MACRO(i++));}SQ_fun(inty){return((y)*(y));}程序运行结果为:SQ_fun:1491625SQ_MACRO:212308.2条件编译条件编译是在编译源文件之前,根据给定的条件决定编译的范围。一般情况下,源程序中所有语句都参加编译。但有时希望在满足一定条件时,编译其中的一部分语句,在不满足条件时编译另一部分语句。这就是所谓的“条件编译”。条件编译对于程序的移植和调试是很有用的。在一套程序要产生不同的版本(如演示版本和实际版本)、避免重复定义时往往使用条件编译。条件编译的三种形式(1)第一种形式:#ifdef标识符程序段1#else程序段2#endif功能是:如果标识符是已被#define命令定义过的宏名,就对程序段1进行编译;否则对程序段2进行编译。#ifdef标识符程序段#endif例8-9根据需要设置条件编译,使之能控制对一些提示信息的输出。#defineDEBUGmain(){inta=4;#ifdefDEBUGprintf(Nowtheprogrammerisdebuggingtheprogram.);#elseprintf(a=%d.,a);#endif}程序运行结果为:Nowtheprogrammerisdebuggingtheprogram.若没有第一行的宏定义命令,程序运行后会输出:a=4.(2)第二种形式:#ifndef标识符程序段1#else程序段2#endif功能是:如果标识符未被#define命令定义过的宏名,就对程序段1进行编译;否则对程序段2进行编译。(3)第三种形式:#if常量表达式程序段1#else程序段2#endif功能是:如果常量表达式的值为真(非0),就对程序段1进行编译;否则对程序段2进行编译。例8-10设置一个开关,判断输入值是半径还是边长,实现求圆或正方形的面积。#defineR1main(){floatc,r,s;printf(inputanumber:);scanf(%f,&c);#ifRr=3.14159*c*c;printf(areaofroundis:%f\n,r);#elses=c*c;printf(areaofsquareis:%f\n,s);#endif}程序运行情况如下:inputanumber:3↙areaofroundis:28.274309若程序的第一行改为:#defineR0则程序运行情况如下:inputanumber:3↙areaofsquareis:9.0000008.3文件包含所谓文件包含是指在一个文件中包含另一个文件的全部内容,使之成为该文件的一部分。这相当于是两个文件的合并。文件包含由文件包含命令#include来实现,其一般格式为:#include文件名/*格式一*/或#include文件名/*格式二*/其中“文件名”是指被包含的文件,称为头文件。头文件必须是文本文件,如C语言源程序文件等。头文件常以“.h”为后缀(h为head的缩写),但也可以是“.c”或其他,甚至没有后缀也是可以的。区别格式一和格式二的主要区别是在存放头文件的路径上。①使用格式一时,预处理程序只在系统规定的目录(include子目录)中去查找指定的头文件,若找不到,则出错,这称
本文标题:08 编译预处理(最终讲稿)
链接地址:https://www.777doc.com/doc-3160709 .html