《C语言程序设计基础与应用第8章.ppt》由会员分享,可在线阅读,更多相关《C语言程序设计基础与应用第8章.ppt(58页珍藏版)》请在三一办公上搜索。
1、第8章 预处理,8.1 概述8.2 宏定义8.3 文件包含8.4 条件编译8.5 运行一个多文件的程序8.6 常见错误分析,8.7 小结习题实验与实训,在前面各章中,我们已多次使用过以符号“#”开头的预处理命令。例如包含命令#include,宏定义命令#define等。在源程序中,这些命令都放在函数之外,且一般是放在源文件的前面,它们被称为预处理部分。预处理是语言的一个重要功能,它由预处理程序负责完成。所谓编译预处理,是指在对源程序进行编译之前,先对源程序中的编译预处理命令进行处理;然后再将处理的结果与源程序一起进行编译,得到目标代码。,8.1 概述,语言提供了多种预处理功能,例如宏定义、文件
2、包含、条件编译等。合理地使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。本章将介绍常用的几种预处理功能。,在语言源程序中允许用一个标识符来表示一个字符串,称为“宏”。被定义为宏的标识符称为“宏名”。在编译预处理时,对程序中所有出现的宏名,都用宏定义中的字符串去替换,被称为“宏代换”或“宏展开”。,8.2 宏定义,使用宏定义可以提高源程序的可维护性、可移植性,以及减少源程序中重复书写字符串的工作量。宏定义是由源程序中的宏定义命令完成的。宏替换是由预处理程序自动完成的。在语言中,宏分为带参数宏和无参数宏两种。下面分别讨论这两种宏的定义和调用。,1.定义宏名后不带参数的宏
3、为无参数宏。无参数宏定义的一般形式为#define 宏名 字符串其含义是将在程序中出现“宏名”的地方均用“字符串”来替代。前面使用过的符号常量的定义就是一种无参数宏定义。例如,#define N 10。又例如,#define M(y*y+3*y),该宏定义表示在程序中出现M的地方均用表达式(y*y+3*y)替代。,8.2.1 无参数宏,例题8.1 格式化输入/输出。#define FORMAT%f,%f,%f,%fmain()floatx1,x2,x3,x4;printf(Please enter 4 float numbers:);scanf(FORMAT,说明 本例题中两次用到格式控制符%
4、f,%f,%f,%f。通过使用宏,简化了源程序,同时又不易出错。,例题8.2 宏定义的正确使用。#define M(y*y+3*y)main()int result,y;printf(Input a number:);scanf(%d,说明 上例程序中要注意的是,在宏定义中表达式(y*y+3*y)两边的括号不能少,否则会发生错误。,2.使用宏应注意的问题(1)宏定义不是说明或语句,在行末不必加分号,如果加上分号则会连分号也一起被置换。(2)宏替换的作用范围是定义它的源文件。(3)宏名通常使用大写字母表示。(4)宏定义必须写在函数之外,其作用域为自宏定义命令起到源程序结束。如果要终止其作用域,可
5、以使用#undef命令。例如:,#define PI 3.14159main().#undef PIf1().这里,PI只在main()有效,在f1函数中无效。,(5)宏名在源程序中若用引号括起来,则预处理程序不对其作宏替换。例如:#define OK 100main()printf(OK);printf(n);虽然定义宏名OK表示100,但在printf语句中OK被引号括起来,因此不作宏替换。,语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中不仅要宏展开,而且要用实参去替换形参。带参数宏的定义形式一般为:#define 宏名(形参表)字符
6、串,其含义是作相应的参数替换。带参数宏调用的一般形式为:宏名(实参表);,8.2.2 带参数宏,例题8.3 带参数宏的使用。#define MAX(a,b)(a b)?abmain()int num_1,num_2,max;printf(Input two numbers:);scanf(%d%d,说明 上例程序的第1行进行带参数宏定义,第7行max=MAX(num_1,num_2)为宏调用,实参num_1,num_2将分别替换形参a,b。,注意(1)带参数宏定义中,宏名和形参表之间不能有空格。例如,本例中的宏如果写为#define MAX(a,b)(ab)?ab,将被认为是无参数宏定义,宏名
7、MAX代表字符串(a,b)(ab)?a:b。(2)定义中的形参是标识符,而宏调用中的实参可以是表达式。例如:,#define T(y)(y)*(y)/*形参y是标识符*/main()int num,result;printf(Input a number:);scanf(%d,(3)函数调用与带参数宏的异同 相似点函数调用与宏调用均在其名称(函数名、宏名)后的括号内列出实参;要求实参与形参的数目相等。不同点函数调用时,要把实参表达式的值计算出来再赋予形参;而宏替换中对实参表达式不作计算,直接照原样进行替换即可。函数调用是在程序运行时处理的,分配临时的内存单元。宏替换是在编译时进行的,在展开时并
8、不分配内存单元,不进行值的传递处理。,函数中的实参与形参均要定义类型,并要求类型一致;宏不存在类型问题,宏名无类型,其参数也无类型,宏定义时,字符串可以是任何类型的数据。函数调用只能得到一个返回值,而宏调用可得到多个结果。,例题8.4 函数调用与宏调用的比较。程序段1#define PI 3.1415926main()float r,girth,area;float cir1(float);float cir2(float);printf(Please enter radius:);scanf(%f,float cir1(float y)/*函数调用*/return(2*PI*y);float
9、 cir2(float y)/*函数调用*/return(PI*y*y);程序段2#define PI 3.1415926#define CRI(r,girth,area)girth=2*PI*r;area=PI*r*r,main()float r,girth,area;printf(Please enter radius:);scanf(%f,说明 从以上两个代码段可见,要想通过函数调用获得两个值周长和面积,就必须使用两个函数调用,因为函数调用只能得到一个返回值。而宏调用则不同,它可得到多个值。,例题8.5 下面的程序展示了宏的嵌套定义和替换。#include stdio.h#define
10、PI(3.1415926536)#define FUN(k)(k)*PI#define PR(value)printf(Valuefn,(value)main()float area;area PI*6.8*6.8;PR(area);PR(FUN(2)*6.8);说明 本例使用了宏的嵌套定义,在宏展开时由预处理程序层层替换。,文件包含是C预处理程序的另一个重要功能。文件包含命令行的一般形式和示例如下:格式#include 文件名或#include 示例#include stdio.h#include math.h#include,8.3 文件包含,文件包含命令的功能是把指定的文件与当前的源程序
11、文件连成一个源文件。在程序设计中,文件包含是非常有用的。一个大的程序可以分为多个模块,由多个程序员分别编程。有些公用的符号常量或宏定义等可以单独组成一个文件,在其他文件的开头用包含命令包含该文件即可使用。这样,就可以避免在每个文件开头都去书写那些公用量,从而节省时间、减少差错。,对文件包含命令的说明如下:(1)包含命令中的文件名可以用双引号括起来,也可以用尖括号括起来。例如,以下写法都是允许的:#include“stdio.h”或#include。但是,这两种形式是有区别的。(2)一个include命令只能指定一个被包含文件,若有多个文件要包含,则需要使用多个include命令。(3)文件包含
12、允许嵌套,即在一个被包含的文件中又可以包含另一个文件。,可以用#include指令包含扩展名不是.h的文件吗?我们可以用#include命令包含任何一个文件。如果一个程序是由多个源程序文件构成的,就可以在一个源程序文件中使用#include命令把其他程序文件都包含进来,这是解决多文件程序的一种有效的方法。当然,为了程序的可读性考虑,最好不要把扩展名不是.h的文件用#include命令包含进来,这样不容易区分哪些文件是用于编译预处理的。,预处理程序提供了条件编译的功能。按不同的条件可以编译不同的程序部分,因而产生不同的目标代码文件。条件编译有3种形式,以下分别介绍。,8.4 条件编译,1.第一种
13、形式#ifdef 标识符程序段1#else 程序段2#endif 其功能是,如果标识符已被#define命令定义过,则对程序段1进行编译;否则,对程序段2进行编译。,例题8.6 条件编译使用之一。#define Ready 1main()#ifdef Ready printf(Yes!n);#else printf(No!n);#endif,2.第二种形式#ifndef 标识符 程序段1#else 程序段2#endif 其功能是,如果标识符未被#define命令定义过,则对程序段1进行编译;否则,对程序段2进行编译。这与第一种形式的功能正相反。,例题8.7 条件编译使用之二。#define H
14、eadmain()#ifndef Head printf(Error!n);#else printf(Ok!n);#endif,3.第三种形式#if 常量表达式程序段1#else 程序段2#endif其功能是,如果常量表达式的值为真(非0),则对程序段1进行编译;否则,对程序段2进行编译。因此可以使程序在不同条件下完成不同的功能。,例题8.8 条件编译使用之三。#define R 1#define PI 3.1416main()float r,cir_are,squ_are;printf(Input a number:);scanf(%f,#else squ_are=r*r;printf(Ar
15、ea of square is:%fn,squ_are);#endif上面介绍的条件编译当然也可以用条件语句来实现。但是,用条件语句将会对整个源程序进行编译,生成的目标代码程序很长;而采用条件编译,则会根据条件只编译其中的程序段1或程序段2,生成的目标程序较短。如果条件选择的程序段很长,采用条件编译的方法是十分必要的。,在第7章中,我们利用外部函数实现了将多个C语言模块组合成一个程序,本章我们将利用编译指令,分模块设计来完成这个工作。下面仍以第7章的例题7.8为例,对其进行改写。,8.5 运行一个多文件的程序,例题8.9 多文件程序的运行。(1)首先将F11.C,F2_1.C和F2_2.C分别
16、输入计算机,编译后存盘,例如放在D盘的ex目录下。F2_1.C/*定义一个fun1()函数*/extern int num;void fun1(int x)num=num+x;printf(After fun1,num=%dn,num);F2_2.C/*定义一个fun2()函数*/extern num;,void fun2(char c)num=num+c-0;printf(After fun2,num=%dn,num);F11.C/*定义main()主函数*/#include int num;main()printf(Please enter a number:);scanf(%d,(2)在
17、同一目录下编写两个头文件。f21.h/*定义fun1()函数的头文件*/#ifndef _F21_H void fun1(int x);#endif f22.h/*定义fun2()函数的头文件*/#ifndef _F22_H void fun2(char c);#endif,(3)在文件F11.C的命令行中加入上述两个头文件,即#include d:exf21.h#include d:exf22.h这样,在编译时,系统自动将这2个文件包含到main()函数的前头,作为一个整体编译,而不是分3个文件编译。这时,这些函数会被认为是在同一个文件中,不再作为外部函数被其他文件调用。main()函数中的
18、extern声明可以不要了。,(4)选择File菜单的New命令,在同一目录下建立一个新的文件,即项目文件,取名为f.prj,其内容只有3行,然后存盘。d:exF11.Cd:exF2_1.Cd:exF2_2.C必要时,还应在文件名前加上盘符和路径,例如本例中是放在D盘的ex目录下。(5)在Project菜单下添加项目文件d:exf.prj,按功能键F9进行编译连接;按Ctrl+F9键运行。(6)Alt+F5观察结果。,问题1 文件包含指令中缺少符号“#”。在宏定义中缺少符号“#”,如include,这时编译会产生错误 Declaration Syntax Error。问题2 文件包含指令中多添
19、加了分号。在宏定义指令的末尾添加了分号,例如“include;”是不符合语法规定的。,8.6 常见错误分析,问题3 文件包含指令中错误地使用了尖括号。当我们自己编写了头文件后需要在文件中包含时,例如例题8.9中的头文件d:exf21.h,若以尖括号方式包含即#include,系统就会到存放C库函数头文件所在的目录中寻找此文件,这样就会出现错误。因此,对自己编写的头文件应使用双引号,即#included:exf21.h。问题4 在宏命令中错误地使用了等号。在使用define命令时错误地使用了等号,例如#define PI=3.1416。正确的写法是#define PI 3.1416。,问题5 在
20、头文件中定义变量。前面我们谈到一个变量可以被说明多次,但只能被定义一次。因此,不应该在头文件中定义一个变量。因为,一个头文件可能会被一个程序的多个源文件包含,这样就会被反复定义。,预处理功能是语言特有的功能,它是在对源程序正式编译前由预处理程序完成的。程序员在程序中用预处理命令来调用这些功能。使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计。预处理功能包括宏定义、文件包含和条件编译。宏定义是用一个标识符来表示一个字符串,这个字符串可以是常量、变量或表达式。在宏调用中将用该字符串替换宏名。,8.7 小结,宏定义可以带有参数,宏调用时是以实参替换形参,而不是“值传递”。为了
21、避免宏替换时发生错误,宏定义中的字符串应加上括号,字符串中出现的形式参数两边也应添加括号。文件包含是预处理的一个重要功能,它可用来把多个源文件连接成一个源文件进行编译,结果将生成一个目标文件。条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销,提高了程序的效率。,8-1 写出下列各程序段的运行结果。(1)#include#define X 5#define Y X+1#define Z Y*X/2main()int a;a=Y;printf(%d%dn,Z,-a);,习题,(2)#include#define TRUE 1#define FALSE 0#de
22、fine SQ(x)(x)*(x)main()int num;int again=1;printf(program will stop if input value less 50.n);while(again)printf(please enter number:);scanf(%d,printf(The square for this number is%dn,SQ(num);if(num=50)again=TRUE;else again=FALSE;(3)#include#define LAG#define SMA#define EQ=,main()int i=10,j=20;if(i
23、LAG j)printf(%d larger than%dn,i,j);else if(i EQ j)printf(%d equal to%dn,i,j);else if(i SMA j)printf(%d smaller than%dn,i,j);else printf(No such value.n);,(4)#include#define MAX#define MAXI(x,y)(xy)?xy#define MINI(x,y)(xy)?y xmain()int a=10,b=20;#ifdef MAXprintf(40:The larger one is%dn,MAXI(a,b);#el
24、seprintf(40:The lower one is%dn,MINI(a,b);#endif,#ifndef MINprintf(40:The lower one is%dn,MINI(a,b);#elseprintf(40:The larger one is%dn,MAXI(a,b);#endif#undef MAX#ifdef MAXprintf(40:The larger one is%dn,MAXI(a,b);#elseprintf(40:The lower one is%dn,MINI(a,b);#endif#define MIN,#ifndef MINprintf(40:The
25、 lower one is%dn,MINI(a,b);#elseprintf(40:The larger one is%dn,MAXI(a,b);#endif,【实训目的】1 掌握宏定义的方法。2 掌握文件包含的处理方法。3 掌握条件编译的方法。【实训内容】1 下面定义的宏用来互换两个参数的值。在主函数中调用此宏,分别完成两个整数、实数的互换,输出已交换后的值。,实验与实训,#define CHANGE(a,b)t=b;b=a;a=t;main()int a,b;float c,d;-int t;printf(Enter integer a-,float t;printf(Enter floa
26、t c(1)输入并运行该程序,观察运行结果,体会宏定义实现的过程。比较宏调用与函数调用的区别。(2)考虑若将上述代码中和处的大括号去掉,情况会怎样?,2 输入并运行下面的程序,体会编译程序对包含文件的处理过程。(1)在当前目录中建立下面的头文件head.h。#define OK passed#define ERROR errorint g;(2)在当前目录中建立下面的程序文件。,#include stdio.h#include head.hmain()int k=1;g=5;printf(g=%d k=%dn,g,k);printf(OK=%s ERROR=%sn,OK,ERROR);包含文件head.h的处理是将文件内容就地展开,相当于定义了两个字符串常量和一个全局整型变量。,3 用条件编译方法实现以下功能:定义一个控制条件编译的宏,对输入的一个字符串,或者按原文输出,或者按倒序输出。要求:(1)定义宏ORDER,控制字符串的输出方式;(2)定义倒序输出字符串的函数reverse(char*);(3)编写主函数,用条件编译实现对输出方式的控制。,