C语言中级培训C语言中级培训十一、预处理预处理的概念所谓预处理,就是在编译前由编译预处理器对C源程序中的预处理命令进行处理的过程。源程序中的预处理命令进行处理的过程。C语言中,以“#”开头的语句统称为编译预处C语言中,以“#”开头的语句统称为编译预处理命令。这些命令必须在一行的开头以“#”开始,末尾以“#”开始,末尾不加分号,并且每条命令独占一行不加分号,并且每条命令独占一行,以区别于一不加分号,并且每条命令独占一行不加分号,并且每条命令独占一行,以区别于一般的C语句。它们可以放在程序的任何位置。般的语句。它们可以放在程序的任何位置。2为什么要用”预处理”C语言始终遵循“最经济地使用资源”的最小原则,让C只具有必要的成分,所有非必要的功能都以“补丁”的方式,由语言之外的预处理命令或函数提供。丁”的方式,由语言之外的预处理命令或函数提供。于是就有了一系列的预处理命令,和庞大的函数库。就连处理预处理命令的预处理器也独立于编译器。就连处理预处理命令的预处理器也独立于编译器。这种小巧的模块化语言结构,使得程序的组织方式非这种小巧的模块化语言结构,使得程序的组织方式非常灵活方便。3C语言的预处理命令宏定义#define#undef#define,#undef文件包含#include条件编译条件编译#if/#ifdef/ifndef#else#endif其他4“预处理”前的预处理其实,在预处理器运行之前,编译器要对程序作“预处理”前的预处理工作。具体内容是:1.先把源代码中出现的各种字符映射到源字符集;先把源代码中出现的各种字符映射到源字符集;2.查找\且紧跟换行符的情况,找到了就将其删除。即把下面的两个物理行即把下面的两个物理行print(“That`swond\//有的系统是/,如VC++erful!\n”);erful!\n);变成一个逻辑行:print(“That`swonderful!\n”);3将文本划分成“预处理语言符号”和空白序列。3.将文本划分成“预处理语言符号”和空白序列。如:int/*这里有一些字符*/fox;变成f5变成intfox;然后才进入预处理器运行。什么是宏宏只是一种定义,它给一个语句块(又称替换列表、字符串)定义了一个名字。表、字符串)定义了一个名字。#definePI(3.1415926)宏定义指令宏名字符串当预编译时,编译器首先要执行一个“替换”动作:把源程序中使用宏名的地方替换成宏定义的语句块,就像文本文件替换一样。这个动作术语叫“宏展开”或“宏替换”。宏分为两种无参宏6-无参宏-带参数的宏宏定义普通宏#definePI(31415926)#definePI(3.1415926)带参数的宏#definemax(ab)((a)(b)?(a):(b))#definemax(a,b)((a)(b)?(a):(b))取消宏定义#undefMacroName#undefMacroName宏定义的特点:宏定义的特点-普通宏定义是使用宏名代替一个字符串,不做语法检查,亦不分配空间;-对程序中用双括号括起来的字符串内部的字符,即使与宏名相同,也不进行替换;7-宏可以嵌套。为什么要用宏定义程序的易读性-文字名称比数字要容易理解得多,一个好的宏名文字名称比数字要容易理解得多,一个好的宏名可以望文知义。最好不要在程序中出现数字式的“硬编码”,如:-最好不要在程序中出现数字式的“硬编码”,如:BYTEabName[120];为这个120声明一个宏吧!-为这个120声明一个宏吧!#defineNAME_LENGTH_MAX(120)程序的易维护性同样像上面的例子,如果该宏多处被用到,想变更-同样像上面的例子,如果该宏多处被用到,想变更Name的最大长度为200,那么只需一次变更宏的定义就可以了,而不必处处修改“硬编码”:8义就可以了,而不必处处修改“硬编码”:#defineNAME_LENGTH_MAX(200)减少系统开销(用宏来代替函数)在函数调用的时候会带来重大的系统开销,因此我们在函数调用的时候会带来重大的系统开销,因此我们有时希望有一个程序块,看上去像一个函数,但却没有函数调用的开销,如:#definemax(ab)(((a)=(b))?(a):(b))#definemax(a,b)(((a)=(b))?(a):(b))9使用宏的弊端:1.宏展开时的结果是难以预料的。例如,还是这个宏:例如,还是这个宏:#defineMAX(a,b)(a)(b)?(a):(b)其实这样也还是存在b。如果这样调用:其实这样也还是存在bug。如果这样调用:MAX(i++,j++);经过这个宏以后,i和j都被累加了两次,这绝不是我们想要的。102.由于宏定义在展开时并不进行类型的检查,因此在将宏赋值给其他变量时,这样可能导致丢失此在将宏赋值给其他变量时,这样可能导致丢失精度或变量越界。例如:#defineMAXLEN(256)#defineMAX_LEN(256)unsignedcharc=0;c=MAXLEN;//不报错,但结果错。c=MAX_LEN;//不报错,但结果错。解决方法是,可以定义const类型的变量,由于t类型变量是有类型的,因此编译时编译器会进const类型变量是有类型的,因此编译时编译器会进行类型检查,如果有越界的可能则会提出警告。例如constintMAX_LEN=256;unsignedcharc=0;11c=MAX_LEN;//此时编译器会提出警告3.对于带参数的宏,虽然其执行速度很快(因为没有函数调用的开销),但宏会让源代码体积膨没有函数调用的开销),但宏会让源代码体积膨胀,使目标文件尺寸变大。如:一个50行的宏,程序中有1000处地方用到它,-如:一个50行的宏,程序中有1000处地方用到它,宏展开后会很不得了,反而不能让程序执行得很快(因为执行文件变大,运行时系统换页频繁)。因为执行文件变大,运行时系统换页频繁)。12使用宏定义时需要注意的要点:1:别忘了给参数加上括号。对于不带参的宏,若宏值多于一项,一定要使用括号:对于不带参的宏,若宏值多于一项,一定要使用括号:#defineMAX(M+N)要给每个参数加上括号,否则可能影响计算的优先级#defineabs(x)(x=0)?x:-x()()z=abs(a+b);/*相当于z=(a+b=0)?a+b:-a+b修改后:修改后:#defineabs(x)((x)=0?(x):-(x))13宏替换的弊端:宏替换的弊端:#definePF(x)x*x/*#definePF(x)(x)*(x)*/()()()/*#definePF(x)((x)*(x))*/main()注意替换时不求值,{inta=2,b=3,c;注意替换时不求值,只是字符串的原样替换c=PF(a+b)/PF(a+1);printf(\nc=%d,c);}}按第一种宏定义:;按第一种宏定义:c=a+b*a+b/a+1*a+1;按第二种宏定义:c=(a+b)*(a+b)/(a+1)*(a+1);14按第二种宏定义:()()()();按第三种宏定义:c=((a+b)*(a+b))/((a+1)*(a+1));多用括号就万事大吉了吗?多用括号就万事大吉了吗?如:#definemax(ab)(a)(b)?(a):(b)#definemax(a,b)(a)(b)?(a):(b)当intx=5,y=0;时max(++x,y);就成了(++x)(y)?(++x):(y)其中的x加了两遍。其中的加了两遍。而当max(++x,y+10);展开为(++)(+10)?(++)(+10)(++x)(y+10)?(++x):(y+10)x只加了一遍。可见只要使用带参宏,参数只要写表达式,总会出现麻烦,多用括号也无济于事。15现麻烦,多用括号也无济于事。2:在定义宏时不要为宏加分号。#defineassert(e)\续行符#defineassert(e)\{if(!e)assert_error(__FILE__,__LINE__);};if(x0&&Y0)if(x0&&Y0)assert(xy);//这里会有两层分号else//由于有两层分号,导致else无法与if配对assert(yx);(y);3:宏定义写在函数的花括号外边,作用域为其后的程序,通常在文件的最开头。的程序,通常在文件的最开头。4:可以用#undef命令终止宏定义的作用域。16:可以用命令终止宏定义的作用域。5:字符串“”中永远不要包含宏。即如果宏名在源程序中若用引号括起来,则预处理程序不对在源程序中若用引号括起来,则预处理程序不对其作宏代换。如:#defineOK(100)()main(){{printf(OK);printf(\n);p()}上例中定义宏名OK表示100,但在printf语句中OK上例中定义宏名表示,但在p语句中被引号括起来,因此不作宏代换。程序的运行结果为:OK。这表示把“OK”当字符串处理。176:宏定义不分配内存,变量定义分配内存。宏展开只作替换,不做计算,不做表达式求解。展开只作替换,不做计算,不做表达式求解。7:对于带有参数的宏,定义时宏名和参数的括号间一定不能有空格。号间一定不能有空格。#definef(x)(x)+1//实际上是把f定义成了:(x)(x)+18:尽量用typedef而不是宏定义去定义类型。189:宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序使用已经定义的宏名。在宏展开时由预处理程序层层代换。例如:例如:#definePI3.1415926#defineS(PI)*(y)*(y)//PI是已定义的宏名对于语句:对于语句:printf(%f,S);宏代换后变为:if(%f3141926**)宏代换后变为:printf(%f,3.1415926*y*y);注意:虽然宏定义允许嵌套,但不可递归。19注意:虽然宏定义允许嵌套,但不可递归。宏定义实例——无参宏定义举例宏的作用域以及无参宏的使用方法:#defineR(3.0)#definePI(3.1415926)#defineL(2)*(PI)*(R)#definebegin{#defineend}#defineforeverfor(;;)idi()voidmain()begin/*{*/printf(L=%fL);printf(L=%f,L);#undefPI/*取消对PI的宏定义*/forever;/*for(;;);无限循环*/20forever;/for(;;);无限循环/end/*}*/宏定义实例——带参数的宏定义举例宏参数中括号的作用:#defineS1(a,b)a*b#defineS2(a,b)((a)*(b))#definemax(a,b)((a)(b)?(a):(b))voidmain(){intx=3,y=4,i=5,j=6,s,z;S1(+)/*+*11*/s=S1(x+y,x-y);/*s=x+y*x-y;s==11*/s=S2(x+y,x-y);/*s=((x+y)*(x-y));s==-7*/z=max(i++j++);/*z=((i++)(j++)?(i++):(j++));*/z=max(i++,j++);/*z=((i++)(j++)?(i++):(j++));*//*z==7,i==6,j==8*/}21}宏定义实例——用宏定义构建机制另外一种使用宏的方法是:利用一组宏来构建一套机制,然后通过执行这一组宏来执行这一机制。典机制,然后通过执行这一组宏来执行这一机制。典型的用法如MFC中的消息处理机制。例如下面的一组宏(宏的定义请参照本页备注):BEGINMESSAGEMAP(CMainFrame,CFrameWnd)__(,)ON_WM_CREATE()ON_COMMAND(ID_EDIT_COPY,OnEditCopy)ON_WM_CLOSE()END_MESSAGE_MAP()22BEGIN_MESSAGE_MAP有两个版本,分别用于静