3Sept.2008©NeusoftConfidentialC语言中级第八章:预处理目标:1)掌握预处理的概念2)领会文件包含的意义3)了解头文件的结构和内容4)理解无参宏和带参宏5)学会使用条件编译时间:学时教学方法:PPT+练习8.1预处理的概念在编译过程开始之前,C语言预处理器首先对程序代码做了必要的转换处理。我们称这种转换处理为预处理。8.1预处理的概念C语言中,以“#”开头的语句统称编译预处理命令。这些命令必须在一行的开头以“#”开始,末尾不加分号,并且每条命令独占一行,以区别于一般的C语句,它们可以放在程序的任何位置。8.1预处理的概念预处理前的处理其实,在预处理器运行之前,编译器要对程序进行几次翻译工作,然后才进入预处理器运行。具体内容是:1.先把源代码中出现的各种字符映射到源字符集。例如:inta??(10??)={1,2,3};??=#??([??!|??)]8.1预处理的概念2.查找\换行符的情况,找到了就将其删除。例如:把下面的两个物理行printf(That’swond\erful!\n);变成一个逻辑行:printf(That’swonderful!\n);3.消除一些空白符和注释,替换为空格。例如:int/*这里有一些字符*/fox;变成:intfox;8.2什么是宏宏是一种定义,它就是给一个语句块(宏体)定义了一个名字。#define宏名宏体宏定义的好处?1.提高了可读性:数字本身并没有什么实际的意义。例如:#definePI3.14159262.减少了书写错误:我们用#define定义个常量PI代表3.1415926,用宏定义后,只要一处校验,其它相同的错误也解决了。8.2什么是宏3.维护性:有时我们需要将某个特定数据,在程序中出现的所有实例加以修改,我们只做一个改动就达到目的。#defineMAXSIZE1004.提高运行速度:在函数调用的时候会带来重大的系统开销,因此我们有时希望有一个程序块,看上去像一个函数,但却没有函数调用的开销#definemax(a,b)(((a)(b))?(a):(b))8.2什么是宏#definePI3.1415voidset(void);voidmain(void){floatf1=PI;}#undefPIvoidset(void){floatf2=PI;}宏也有作用域预处理时执行“替换”动作:把源程序中使用宏名的地方替换成宏体。这个过程叫“宏展开”或“宏替换”。8.3无参宏和带参宏宏分为两种:1.无参宏2.带参宏,区别宏名字后有“参数”就是带参宏,也叫宏函数。#includestdio.hvoidmain(void){printf(%d\n,NUM);}#defineN2#defineMN+1#defineNUM(M+3)*M/2128.3无参宏和带参宏宏分为两种:1.无参宏2.带参宏#includestdio.h#defines(a,b)a*bvoidmain(void){printf(%d\n,s(1,2));}#undefsprintf(%d\n,s(1,2));28.3无参宏和带参宏一般形式:#define宏名(参数表)宏体宏展开:“形参”用“实参”替换,其它字符保留。带参数的宏(带参宏)不能用空格隔开8.4宏定义的特点1.宏定义不是C语句;2.宏定义不是typedef;3.宏定义不是函数。宏定义就是使用宏名代替一个字符串,在预编译时替换。注:对程序中用双引号括起来的字符串内部的字符,即使与宏名相同,也不进行替换。#includestdio.h#defineRHiintmain(void){printf(R);return1;}8.4宏定义的特点宏不是类型定义#defineSTUstructstudent*STUa,b;typedefstructstudent*STU;STUa,b;宏和typedef的区别:8.4宏定义的特点#definemax(a,b)(((a)(b))?(a):(b))intmain(void){intx[]={2,3,1};intbiggest=x[0];inti=1;biggest=max(biggest,x[i++]);printf(%d\n,biggest);return1;}宏替换时,赋值语句扩展为:biggest=((biggest)(x[i++])?(biggest):(x[i++]));宏并不是函数8.4宏定义的特点函数调用和宏定义的区别函数调用宏定义程序在处理时,先求出实参表达式的值,然后代入形参。带参宏只是进行简单的字符替换。编译时会对实参进行类型检查带参宏就是字符串替换函数调用不会使源程序变长宏展开会使源程序变长。函数调用占运行时间(建栈、退栈)。宏替换不占运行时间,只占预编辑时间。8.4宏定义的特点#definePF(x)((x)*(x))voidmain(void){inta=2,b=3,c;c=PF(a+b)/PF(a+1);printf(\nc=%d,c);}宏替换的弊端#definePF(x)x*x#definePF(x)(x)*(x)8.4宏定义的特点按第一种宏定义:c=a+b*a+b/a+1*a+1;按第二种宏定义:c=(a+b)*(a+b)/(a+1)*(a+1);按第三种宏定义:c=((a+b)*(a+b))/((a+1)*(a+1));1.对于不带参的宏,若宏值多于一项,一定要使用括号:#defineMAX(M+N)2.对于带参的宏,要给每个参数加上括号,否则可能影响计算的优先级。8.5条件编译一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是“条件编译”。预编译器保留参加编译的源程序,不参加编译的用空白字符替换。8.5条件编译方式2:#ifndef标识符程序段1#else程序段2#endif方式3:#if表达式程序段1#else程序段2#endif方式1:#ifdef标识符程序段1#else程序段2#endif作用是:当标识符已经被定义过,则编译程序段1,否则编译程序段2,#else部分可以省略。作用是:当标识符没有被定义过,则编译程序段1,否则编译程序段2,#else部分可以省略。作用是:当表达式的值为真,则编译程序段1,否则编译程序段2。8.5条件编译条件编译方式1举例:在工程的DEBUG版本中打印调试信息/*********test.c***************/#ifdefDEBUG/*做些打印工作如printf....,这部分工作在调试版本中进行,发布版不需要*/#else//else分支可省略,一般都省略#endif/*********test.c***************/使用时如果想打印调试信息,则加上#defineDEBUG就可以完成打印调试信息的功能8.5条件编译条件编译方式2举例:用来防止一个源文件对同一个头文件的多次重复包含(思考什么是包含头文件?)/*********config.h***************/#ifndef_CONFIG_H_#define_CONFIG_H_#includestdio.h#includestdlib.h//......等等需要的头文件#else//else分支这部分通常省略#endif//*********config.h***************/8.5条件编译条件编译方式3举例:只使有效的代码参加编译,提高效率//某config.h内容如下:#defineFosc16000000#defineFcclk(Fosc*4)#defineFpclk(Fcclk/4)*1//可能是1、2、4//在编译前就能决定下来到底是几//在target.c中需要根据Fpclk、Fcclk的值设置VPBDIV//于是推荐做法如下:#if(Fpclk/(Fcclk/4))==2VPBDIV=2;#elseVPBDIV=1;#endif8.5条件编译不要用#if…/#endif来代替注释1.单行注释可以写成//……或/*………………*/,而#if…#endif不可。(一行只能一条预编译指令)2.注释写了是为了给人们看的,是解释说明。而/#if……/#endif是给预编译器看的,对象不同。8.6文件包含文件包含:就是在预编译时将头文件的内容加入到包含文件中。思考:#includestdio.h“包含”进来了哪些内容?用到哪个函数包含哪个函数?有没有函数的实现?8.6文件包含#include命令的一般形式:(区别?)#include“文件名”#include文件名文件包含可以指定所包含文件的绝对路径:#includeC:\ProgramFiles\MicrosoftVisualStudio\MyProjects\01.h文件包含可以指定所包含文件的相对路径:#include“..\MyProjects\01.h“推荐使用相对路径8.6文件包含#include02.h#include01.h#includestdio.hintmain(void){printf(程序开始!\n);PrintStar(5);return0;}#include01.hvoidPrintStar(intn);#includestdio.hintm=15;1.h2.hfile1.c8.6文件包含文件包含命令会导致重复定义例如:文件file1.c包含了文件1.h和文件2.h,文件2.h包含了文件1.h。……01.h02.h#include“01.h”file1.c#include“02.h”#include“01.h”……intm=15;intm=15;intm=15;intm=15;8.6文件包含解决方法:防止头文件重复包含导致的重复定义。#ifndef_01_H#define_01_H…………#endif01.h这样的话就没错误了吗?变量定义在头文件中可以吗?试着在工程中加入file2.c结论:头文件中不要定义变量,防止头文件重复包含的条件开关只能使得一个源文件不会对该头文件多次包含//file2.c#include01.hvoidPrintStar(intn){inti=0;for(i=0;in;i++)printf(*);printf(\n);}8.7头文件头文件应含有的内容:1.函数原型;2.定义的宏常量和数据类型等各文件都要使用的东西。这样既避免了各文件重复作相同的事情,又可以取得“一改全改”的统一。头文件含有变量定义危险!若对加static修饰,则各个包含该头文件的文件各自拥有自己的一份拷贝。8.7头文件头文件结构头文件由三部分内容组成:(1)头文件的版权和版本声明(参见示例1-1)。(2)预处理块。(3)函数和数据类型定义等。/**Copyright(c)2001,上海贝尔有限公司网络事业部*文件名称:filename.h*文件标识:见配置管理计划书*摘要:简要描述本文件的内容*当前版本:1.1*作者:输入作者(或修改者)名字*完成日期:2009年7月20日*取代版本:1.0*原作者:输入原作者(或修改者)名字*完成日期:2009年6月10日*/8.7头文件8.7头文件#ifndefGRAPHICS_H//防止graphics.h被重复引用#defineGRAPHICS_H#includemath.h//引用标准库的头文件#include“myheader.h”//引用非标准库头文件structbox//结构体类型声明{…};voidFunction1(void);//全局函数声明…#endif小结•预处理的概念•文件包含的意义•头文件的结构和内容•无参宏和带参宏•宏和函数的区别•宏和typedef区别•条件编译的方式练习如果这个符号被定义那么你就调用这个函数OPTION_LONGprint_ledger_long()OPTION_DETAILEDprint_ledger_detailed()(无)p