《c程序第9章-预处理命令.ppt》由会员分享,可在线阅读,更多相关《c程序第9章-预处理命令.ppt(56页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、9.1宏定义宏定义9.2“文件包含文件包含”处理处理9.3条件编译条件编译习题习题第9章 预处理命令ANSI C标准规定可以在标准规定可以在C源程序中加入一些源程序中加入一些“预处预处理命令理命令”(preprocessor DireCtiveS),以改进程,以改进程序设计环境,提高编程效率。这些预处理命令是序设计环境,提高编程效率。这些预处理命令是由由ANSI C统一规定的,但是它不是统一规定的,但是它不是C语言本身的语言本身的组成部分,不能直接对它们进行编译组成部分,不能直接对它们进行编译(因为编译程因为编译程序不能识别它们序不能识别它们)。必须在对程序进行通常的编译。必须在对程序进行通常
2、的编译(包括词法和语法分析、代码生成、优化等包括词法和语法分析、代码生成、优化等)之前,之前,先对程序中这些特殊的命令进行先对程序中这些特殊的命令进行“预处理预处理”,即即根据预处理命令对程序作相应的处理根据预处理命令对程序作相应的处理(例如,若程例如,若程序中用序中用#define命令定义了一个符号常量命令定义了一个符号常量A,则在,则在预处理时将程序中所有的预处理时将程序中所有的A都置换为指定的字符串。都置换为指定的字符串。若程序中用若程序中用#inClude命令包含一个文件命令包含一个文件“StDio.h”,则在预处理时将则在预处理时将StDio.h文件中的实际内容代替该命文件中的实际内
3、容代替该命令令)。经过预处理后程序不再包括预处理命令了,最。经过预处理后程序不再包括预处理命令了,最后再由编译程序对预处理后的源程序进行通常的编后再由编译程序对预处理后的源程序进行通常的编译处理,得到可供执行的目标代码。现在使用的许译处理,得到可供执行的目标代码。现在使用的许多多C编译系统都包括了预处理、编译和连接等部分,编译系统都包括了预处理、编译和连接等部分,在进行编译时一气呵成。因此不少用户误认为预处在进行编译时一气呵成。因此不少用户误认为预处理命令是理命令是C语言的一部分,甚至以为它们是语言的一部分,甚至以为它们是C语句,语句,这是不对的。必须正确区别预处理命令和这是不对的。必须正确区
4、别预处理命令和C语句、语句、区别预处理和编译,才能正确使用预处理命令。区别预处理和编译,才能正确使用预处理命令。C语言与其他高级语言的一个重要区别是可以使用预语言与其他高级语言的一个重要区别是可以使用预处理命令和具有预处理的功能。处理命令和具有预处理的功能。C提供的预处理功能主要有以下三种:提供的预处理功能主要有以下三种:1.宏定义宏定义2.文件包含文件包含3.条件编译条件编译分别用宏定义命令、文件包含命令、条件编译命令分别用宏定义命令、文件包含命令、条件编译命令来实现。为了与一般来实现。为了与一般C语句相区别,这些命令以符语句相区别,这些命令以符号号“#”开头。开头。9.1 宏定义宏定义9.
5、1.1 不带参数的宏定义不带参数的宏定义用一个指定的标识符用一个指定的标识符(即名字即名字)来代表一个字符串,来代表一个字符串,它的一般形式为它的一般形式为#define 标识符字符串。这就是标识符字符串。这就是已经介绍过的定义符号常量。如已经介绍过的定义符号常量。如:#definePI3.1415926它的作用是指定用标识符它的作用是指定用标识符PI来代替来代替“3.1415926”这个字符串,在编译预处理这个字符串,在编译预处理时,将程序中在该命令以后出现的所有的时,将程序中在该命令以后出现的所有的PI都用都用“3.1415926”代替。这种方法使用户能以一个简代替。这种方法使用户能以一个
6、简单的名字代替一个长的字符串,因此把这个标识单的名字代替一个长的字符串,因此把这个标识符符(名字名字)称为称为“宏名宏名”,在预编译时将宏名替换,在预编译时将宏名替换成字符串的过程称为成字符串的过程称为“宏展开宏展开”。#define是宏定是宏定义命令。义命令。例例9.1#define PI3.1415926 main()float l,s,r,v;printf(input raDiuS);scanf(%f,&r);l=2.0*PI*r;s=PI*r*r;v=3.0/4*PI*r*r*r;printf(l=%10.4fnS=%10.4fnv=%10.4fn,l,s,v);运行情况如下:运行情况
7、如下:input radius:4 l=25.1328 s=50.2655 v=150.7966说明:说明:(1)宏名一般习惯用大写字母表示,以便与变量名相宏名一般习惯用大写字母表示,以便与变量名相区别。但这并非规定,也可用小写字母。区别。但这并非规定,也可用小写字母。(2)使用宏名代替一个字符串,可以减少程序中重复使用宏名代替一个字符串,可以减少程序中重复书写某些字符串的工作量。例如,如果不定义书写某些字符串的工作量。例如,如果不定义PI代代表表3.1415926,则在程序中要多处出现,则在程序中要多处出现3.1415926,不,不仅麻烦,而且容易写错仅麻烦,而且容易写错(或敲错或敲错),用
8、宏名代替,用宏名代替,简单不易出错,因为记住一个宏名简单不易出错,因为记住一个宏名(它的名字往往它的名字往往用容易理解的单词表示用容易理解的单词表示)要比记住一个无规律的字要比记住一个无规律的字符串容易,而且在读程序时能立即知道它的含义,符串容易,而且在读程序时能立即知道它的含义,当需要改变某一个常量时,可以只改变当需要改变某一个常量时,可以只改变#define命命令行,一改全改。例如,定义数组大小,可以用令行,一改全改。例如,定义数组大小,可以用#define array-size 1000int arrayarray-size;先指定先指定array-size代表常量代表常量1000,因此
9、数组,因此数组array大小大小为为1000,如果需要改变数组大小,只需改,如果需要改变数组大小,只需改#define行:行:#define array-size 500使用宏定义,可以提高程序的通用性。使用宏定义,可以提高程序的通用性。(3)宏定义是用宏名代替一个字符串,也就是作简宏定义是用宏名代替一个字符串,也就是作简单的置换,不作正确性检查。如果写成单的置换,不作正确性检查。如果写成#define PI 3.l4l59即把数字即把数字1写成小写字母写成小写字母l,预处理时也照样代入,预处理时也照样代入,不管含义是否正确。也就是说预编译时不作任何不管含义是否正确。也就是说预编译时不作任何语
10、法检查。只有在编译已被宏展开后的源程序时语法检查。只有在编译已被宏展开后的源程序时才会发现错误并报错。才会发现错误并报错。(4)宏定义不是宏定义不是C语句,不必在行末加分号。如果加语句,不必在行末加分号。如果加了分号则会连分号一起进行置换。如:了分号则会连分号一起进行置换。如:#define PI 3.1415926;area=PI*r*r;经过宏展开后,该语句为经过宏展开后,该语句为area=3.1415926;*r*r;显然出现语法错误。显然出现语法错误。(5)#define命令出现在程序中函数的外面,宏名的命令出现在程序中函数的外面,宏名的有效范围为定义命令之后到本源文件结束。通常,有效
11、范围为定义命令之后到本源文件结束。通常,#define命令写在文件开头,函数之前,作为文件命令写在文件开头,函数之前,作为文件一部分,在此文件范围内有效。一部分,在此文件范围内有效。(6)可以用可以用#undef命令终止宏定义的作用域。例如:命令终止宏定义的作用域。例如:#define g 8.8 main()|G的有效范围的有效范围#undef g f1()由于由于#undef的作用,使的作用,使G的作用范围在的作用范围在#undef行处终止,因行处终止,因此在此在f1函数中,函数中,g不再代表不再代表8.8。这样可以灵活控制宏定义的。这样可以灵活控制宏定义的作用范围。作用范围。(7)在进行
12、宏定义时,可以引用已定义的宏名,可在进行宏定义时,可以引用已定义的宏名,可以层层置换。以层层置换。例例9.2#define R 3.0#define PI3.1415926#define L2*PI*R#define SPI*R*R main()printf(L=%fns=%fn,L,S);运行情况如下:运行情况如下:L=18.849556 s=28.274333经过宏展开后,经过宏展开后,printf函数中的输出项函数中的输出项L被展开为被展开为2*3.1415926*3.0,S展开为展开为3.1415926*3.0*3.0,printf函数调用语句展开为函数调用语句展开为printf(L=
13、%fns=%fn,2*3.1415926*3.0,3.1415926*3.0*3.0);(8)对程序中用双括号括起来的字符串内的字符,对程序中用双括号括起来的字符串内的字符,即使与宏名相同,也不进行置换。如例即使与宏名相同,也不进行置换。如例9.2中的中的printf函数内有两个函数内有两个l字符,一个在双引号内,它不字符,一个在双引号内,它不被宏置换,另一个在双引号外,被宏置换展开。被宏置换,另一个在双引号外,被宏置换展开。(9)宏定义是专门用于预处理命令的一个专用名词,宏定义是专门用于预处理命令的一个专用名词,它与定义变量的含义不同,只作字符替换,不分它与定义变量的含义不同,只作字符替换,
14、不分配内存空间。配内存空间。9.1.2带参数的宏定义带参数的宏定义不是进行简单的字符串替换,还要进行参数替换。不是进行简单的字符串替换,还要进行参数替换。其定义的一般形式为其定义的一般形式为#define宏名宏名(参数表参数表)字符串字符串字符串中包含在括弧中所指定的参数。如:字符串中包含在括弧中所指定的参数。如:#defines(a,b)a*barea=s(3,2);定义矩形面积定义矩形面积S,a和和B是边长。在程序中用了是边长。在程序中用了s(3,2),把,把3、2分别代替宏定义中的形式分别代替宏定义中的形式图图9.19.1参数参数a、b,即用,即用3*2代替代替s(3,2)。因此赋值语句
15、展开。因此赋值语句展开为为area=3*2;对带参的宏定义是这样展开置换的:在程序中如果对带参的宏定义是这样展开置换的:在程序中如果有带实参的宏有带实参的宏(如如S(3,2),则按,则按#define命令行中命令行中指定的字符串从左到右进行置换。如果串中包含指定的字符串从左到右进行置换。如果串中包含宏中的形参宏中的形参(如如a、b),则将程序语句中相应的实,则将程序语句中相应的实参参(可以是常量、变量或表达式可以是常量、变量或表达式)代替形参,如果代替形参,如果宏定义中的字符串中的字符不是参数字符宏定义中的字符串中的字符不是参数字符(如如a*b中的中的*号号),则保留。这样就形成了置换的字符串
16、,则保留。这样就形成了置换的字符串,见图见图9.1。例例9.3#definePI3.1415926#defineS(r)PI*r*r main()float a,area;a=3.6;area=S(a);printf(r=%fnarea=%fn,a,area);运行结果如下:运行结果如下:r=3.600000area=40.715038赋值语句赋值语句area=s(a);经宏展开后为经宏展开后为area=3.1415926*a*a;说明:说明:(1)对带参数的宏的展开只是将语句中的宏名后面括对带参数的宏的展开只是将语句中的宏名后面括号内的实参字符串代替号内的实参字符串代替#define命令行中
17、的形参。例命令行中的形参。例9.3中语句中有中语句中有S(a),在展开时,找到,在展开时,找到#define命令行命令行中的中的S(r),将,将S(a)中的实参中的实参a代替宏定义中的字符串代替宏定义中的字符串“PI*r*r”中的形参中的形参r,得到,得到PI*a*a。这是容易理解。这是容易理解而且不会发生什么问题的。但是,如果有以下语句:而且不会发生什么问题的。但是,如果有以下语句:area=S(a+B);这时把实参这时把实参a+B代替代替PI*r*r中的形参中的形参r,成为,成为area=PI*a+B*a+B;请注意在请注意在a+B外面没有括外面没有括弧,显然这与程序设计者的原意不符。原意
18、希望得弧,显然这与程序设计者的原意不符。原意希望得到到area=PI*(a+B)*(a+B);为了得到这个结果,应当在定为了得到这个结果,应当在定义时,在字符串中的形式参数外面加一个括弧。即义时,在字符串中的形式参数外面加一个括弧。即#define S(r)PI*(r)*(r)在对在对S(a+B)进行宏展开时,将进行宏展开时,将a+B代替代替r,就成了,就成了PI*(a+B)*(a+B)这就达到了目的。这就达到了目的。(2)在宏定义时,在宏名与带参数的括弧之间不应加空在宏定义时,在宏名与带参数的括弧之间不应加空格,否则将空格以后的字符都作为替代字符串的一部格,否则将空格以后的字符都作为替代字符
19、串的一部分。例如,如果有分。例如,如果有#define s(r)PI*r*r被认为被认为s是符号常量是符号常量(不带参的宏名不带参的宏名),它代表字符串,它代表字符串“(r)PI*r*r”。如果在语句中有。如果在语句中有area=S(a);则被展开为则被展开为area=(r)PI*r*r(a)显然不对了。显然不对了。有些读者容易把带参数的宏和函数混淆。的确,它有些读者容易把带参数的宏和函数混淆。的确,它们之间有一定类似之处,在引用函数时也是在函们之间有一定类似之处,在引用函数时也是在函数名后的括弧内写实参,也要求实参与形参的数数名后的括弧内写实参,也要求实参与形参的数目相等。但是带参的宏定义与
20、函数是不同的。主目相等。但是带参的宏定义与函数是不同的。主要有:要有:(1)函数调用时,先求出实参表达式的值,然后代函数调用时,先求出实参表达式的值,然后代入形参。而使用带参的宏只是进行简单的字符替入形参。而使用带参的宏只是进行简单的字符替换。例如上面的换。例如上面的S(a+b),在宏展开时并不求,在宏展开时并不求a+B的的值,而只将实参字符值,而只将实参字符“a+b”代替形参代替形参r。(2)函数调用是在程序运行时处理的,分配临时的函数调用是在程序运行时处理的,分配临时的内存单元。而宏展开则是在编译时进行的,在展内存单元。而宏展开则是在编译时进行的,在展开时并不分配内存单元,不进行值的传递处
21、理,开时并不分配内存单元,不进行值的传递处理,也没有也没有“返回值返回值”的概念。的概念。(3)对函数中的实参和形参都要定义类型,二者的类对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换。而宏不型要求一致,如不一致,应进行类型转换。而宏不存在类型问题,宏名无类型,它的参数也无类型,存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时代入指定的字符即可。只是一个符号代表,展开时代入指定的字符即可。宏定义时,字符串可以是任何类型的数据。例如:宏定义时,字符串可以是任何类型的数据。例如:#defineCHAR1CHINA(字符字符)#definea 3.
22、6 (数值数值)CHAR1和和a不需要定义类型,它们不是变量,在程不需要定义类型,它们不是变量,在程序中凡遇序中凡遇CHAR1均以均以CHINA代之代之;凡遇凡遇a均以均以3.6代代之,显然不需定义类型。同样,对带参的宏:之,显然不需定义类型。同样,对带参的宏:#defineS(r)PI*r*rr也不是变量,如果在语句中有也不是变量,如果在语句中有S(3.6),则展开后为,则展开后为PI*3.6*3.6,语句中并不出现,语句中并不出现r。当然也不必定义。当然也不必定义r的类型。的类型。(4)调用函数只可得到一个返回值,而用宏可以设调用函数只可得到一个返回值,而用宏可以设法得到几个结果。法得到几
23、个结果。例例9.4#definePI3.1415926#define CIRCLE(r,l,s,v)L=2*PI*R;S=PI*R*R;=4.0/3.0*PI*R*R*R main()float r,l,s,v;scanf(%f,&r);CIRCLE(r,l,S,v);printf(r=%6.2f,l=%6.2f,S=%6.2f,v=%6.2fn,r,l,S,v);经预编译宏展开后的程序如下:经预编译宏展开后的程序如下:main()float r,l,S,v;scanf(“%f”,&r);l=2*3.1415926r;S=3.14159*r*r;v=4.0/3.0*3.1415926*r*r*
24、r;printf(r=%6.2f,l=%6.2f,S=%6.2f,v=%6.2fn,r,l,S,v);运行情况如下:运行情况如下:3.5 r=3.50,l=21.99,S=38.48,v=179.59请注意,实参请注意,实参r的值已知,可以从宏带回的值已知,可以从宏带回3个值个值(l,S,v)。其实,只不过是字符代替而已,将字符。其实,只不过是字符代替而已,将字符r代代替替R,l代替代替L,S代替代替S,v代替,而并未在宏展代替,而并未在宏展开时求出开时求出l、S、v的值。的值。(5)使用宏次数多时,宏展开后源程序长,因为每展使用宏次数多时,宏展开后源程序长,因为每展开一次都使程序增长,而函数
25、调用不使源程序变长。开一次都使程序增长,而函数调用不使源程序变长。(6)宏替换不占运行时间,只占编译时间。而函数调宏替换不占运行时间,只占编译时间。而函数调用则占运行时间用则占运行时间(分配单元、保留现场、值传递、分配单元、保留现场、值传递、返回返回)。一般用宏来代表简短的表达式比较合适。有些一般用宏来代表简短的表达式比较合适。有些问题,用宏和函数都可以。如:问题,用宏和函数都可以。如:#define MAX(x,y)(x)(y)?(x)(y)main()int a,b,c,d,t;t=MAX(a+b,c+d);赋值语句展开后为赋值语句展开后为t=(a+b)(c+d)?(a+b)(c+d);注
26、意:注意:MAX不是函数,这里只有一个不是函数,这里只有一个main函数,在函数,在main函数中就能求出函数中就能求出t的值。的值。这个问题也可用函数来求:这个问题也可用函数来求:int max(int x,int y)return(xy?x y);main()int a,B,C,D,t;t=max(a+B,C+D);max是函数,在是函数,在main函数中调用函数中调用max函数才能求出函数才能求出t的值。的值。请仔细分析以上两种方法。请仔细分析以上两种方法。如果善于利用宏定义,可以实现程序的简化,如事如果善于利用宏定义,可以实现程序的简化,如事先将程序中的先将程序中的“输出格式输出格式”
27、定义好,以减少在输出定义好,以减少在输出语句中每次都要写出具体的输出格式的麻烦。语句中每次都要写出具体的输出格式的麻烦。例例9.5#define PR printf#define NL n#define D%D#define D1 D NL#define D2 D D NL#define D3 D D D NL#define D4 D D D D NL#define S%S main()int a,B,C,D;char string=CHINA;a=1;B=2;C=3;D=4;PR(D1,a);PR(D2,a,B);PR(D3,a,B,C);PR(D4,a,B,C,D);PR(S,string
28、);运行时输出以下结果:运行时输出以下结果:1 12 123 1234 CHINA程序中用程序中用PR代表代表printf。以。以NL代表执行一次代表执行一次“换行换行”操作。以操作。以D代表输出一个整型数据的格式符。以代表输出一个整型数据的格式符。以D1代表输出完代表输出完1个整数后换行,个整数后换行,D2代表输出代表输出2个整个整数后换行,数后换行,D3代表输出代表输出3个整数后换行,个整数后换行,D4代表输代表输出出4个整数后换行。以个整数后换行。以S代表输出一个字符串的格代表输出一个字符串的格式符。可以看到,程序中写输出语句就比较简单了,式符。可以看到,程序中写输出语句就比较简单了,只
29、要根据需要选择已定义的输出格式即可,连只要根据需要选择已定义的输出格式即可,连printf都可以简写为都可以简写为PR。可以参照例可以参照例9.5,写出各种输入输出的格式,写出各种输入输出的格式(例如实型、例如实型、长整型、十六进制整数、八进制整数、字符型等长整型、十六进制整数、八进制整数、字符型等),把它们单独编成一个文件,它相当一个,把它们单独编成一个文件,它相当一个“格式库格式库”,用,用#inClude命令把它命令把它“包括包括”到自己所编的程到自己所编的程序中,用户就可以根据情况各取所需了。序中,用户就可以根据情况各取所需了。显然在写大程序时,这样做是很方便的。显然在写大程序时,这样
30、做是很方便的。9.2 “文件包含文件包含”处理处理所谓所谓“文件包含文件包含”处理是指一个源文件可以将另外处理是指一个源文件可以将另外一个源文件的全部内容包含进来,即将另外的文件一个源文件的全部内容包含进来,即将另外的文件包含到本文件之中。包含到本文件之中。C语言提供了语言提供了#inClude命令用命令用来实现来实现“文件包含文件包含”的操作。其一般形式为的操作。其一般形式为#inClude“文件名文件名”或或#inClude 图图9.2图图9.2表示表示“文件包含文件包含”的含意。图的含意。图9.2(a)为文件为文件file1.C,它有一个,它有一个#inClude 命令,然后还命令,然后
31、还有其他内容有其他内容(以以A表示表示)。图。图9.2(B)为另一文件为另一文件file2.C,文件内容以,文件内容以B表示。在编译预处理时,要对表示。在编译预处理时,要对#inClude命令进行命令进行“文件包含文件包含”处理:将处理:将file2.C的的全部内容复制插入全部内容复制插入到到#inClude 命令处,即命令处,即file2.C被包含到被包含到file1.C中,得到图中,得到图9.2(C)所示的结果。在编译中,所示的结果。在编译中,将将“包含包含”以后的以后的file1.C(即图即图9.2(C)所示所示)作为一作为一个源文件单位进行编译。个源文件单位进行编译。“文件包含文件包含
32、”命令是很有用的,它可以节省程序设命令是很有用的,它可以节省程序设计人员的重复劳动。例如,某一单位的人员往往计人员的重复劳动。例如,某一单位的人员往往使用一组固定的符号常量使用一组固定的符号常量(如如G=9.81,pi=3.1415926,e=2.718,C=),可以把这些宏,可以把这些宏定义命令组成一个文件,然后各人都可以用定义命令组成一个文件,然后各人都可以用#inClude命令将这些符号常量包含到自己所写的命令将这些符号常量包含到自己所写的源文件中。这样每个人就可以不必重复定义这些源文件中。这样每个人就可以不必重复定义这些符号常量。相当于工业上的标准零件,拿来就用。符号常量。相当于工业上
33、的标准零件,拿来就用。例例9.6可以将例可以将例9.5程序改为:程序改为:(1)文件文件format.h#define PRprintf#define NLn#define D%D#define D1 D NL#define D2 D D NL#define D3 D D D NL#define D4 D D D D NL#define S%S(2)文件文件file1.C#inClude format.h main()int a,B,C,D;char string=CHINA;a=1;B=2;C=3;D=4;PR(D1,a);PR(D2,a,B);PR(D3,a,B,C);PR(D4,a,B,
34、C,D);PR(S,string);注意:注意:在编译时并不是作为两个文件进行连接的,在编译时并不是作为两个文件进行连接的,而是作为一个源程序编译,得到一个目标而是作为一个源程序编译,得到一个目标(.oBj)文件。因此被包含的文件也应该是源文件而不应文件。因此被包含的文件也应该是源文件而不应该是目标文件。该是目标文件。这种常用在文件头部的被包含的文件称为这种常用在文件头部的被包含的文件称为“标题文标题文件件”或或“头部文件头部文件”,常以,常以“h”为后缀为后缀(h为为heaD(头头)的缩写的缩写),如,如“format.h”文件。当然不文件。当然不用用“.h”为后缀,而用为后缀,而用“C”为
35、后缀或者没有后缀为后缀或者没有后缀也是可以的,但用也是可以的,但用“h”作后缀更能表示此文件的作后缀更能表示此文件的性质。性质。如果需要修改一些常数,不必修改每个程序,只需如果需要修改一些常数,不必修改每个程序,只需修改一个文件修改一个文件(头部文件头部文件)即可。但是应当注意,即可。但是应当注意,被包含文件修改后,凡包含此文件的所有文件都被包含文件修改后,凡包含此文件的所有文件都要全部重新编译。要全部重新编译。头文件除了可以包括函数原型和宏定义外,也可以头文件除了可以包括函数原型和宏定义外,也可以包括结构体类型定义包括结构体类型定义(见第见第10章章)和全局变量定义和全局变量定义等。等。说明
36、:说明:(1)一个一个inClude命令只能指定一个被包含文件,如命令只能指定一个被包含文件,如果要包含果要包含n个文件,要用个文件,要用n个个inClude命令。命令。(2)如果文件如果文件1包含文件包含文件2,而文件,而文件2中要用到文件中要用到文件3的内容,则可在文件的内容,则可在文件1中用两个中用两个inClude命令分别命令分别包含文件包含文件2和文件和文件3,而且文件,而且文件3应出现在文件应出现在文件2之之前,即在前,即在file1.C中定义:中定义:#inClude file3.h#inClude file2.h 这样,这样,file1和和file2都可以用都可以用file3的
37、内容。在的内容。在file2中不中不必再用必再用#inClude 了了(以上是假设以上是假设file2.h在在本程序中只被本程序中只被file1.C包含,而不出现在其他场合包含,而不出现在其他场合)。(3)在一个被包含文件中又可以包含另一个被包含在一个被包含文件中又可以包含另一个被包含文件,即文件包含是可以嵌套的。例如,上面的文件,即文件包含是可以嵌套的。例如,上面的问题也可以这样处理,见图问题也可以这样处理,见图9.3。它的作用与图。它的作用与图9.4所示相同。所示相同。图图9.3图图9.4(4)在在#inClude命令中,文件名可以用双引号或尖括命令中,文件名可以用双引号或尖括号括起来,如
38、可以在号括起来,如可以在file1.C中用中用#inClude 或或#inClude file2.h都是合法的。二者的区别是都是合法的。二者的区别是用尖括弧用尖括弧(即即形式形式)时,系统到存放时,系统到存放C库函库函数头文件所在的目录中寻找要包含的文件,这称为数头文件所在的目录中寻找要包含的文件,这称为标准方式。用双引号标准方式。用双引号(即即“file2.h”形式形式)时,系时,系统先在用户当前目录中寻找要包含的文件,若找不统先在用户当前目录中寻找要包含的文件,若找不到,再按标到,再按标准方式查找准方式查找(即再按尖括号的方式查找即再按尖括号的方式查找)。一般说,。一般说,如果为调用库函数
39、而用如果为调用库函数而用#inClude命令来包含相关命令来包含相关的头文件,则用尖括号,以节省查找时间。如果的头文件,则用尖括号,以节省查找时间。如果要包含的是用户自己编写的文件要包含的是用户自己编写的文件(这种文件一般都这种文件一般都在当前目录中在当前目录中),一般用双引号。,一般用双引号。(5)被包含文件被包含文件(file2.h)与其所在的文件与其所在的文件(即用即用#inClude命令的源文件命令的源文件file1.C),在预编译后已成,在预编译后已成为同一个文件为同一个文件(而不是两个文件而不是两个文件)。因此,如果。因此,如果file2.h中有全局静态变量,它也在中有全局静态变量
40、,它也在file1.C文件中有文件中有效,不必用效,不必用extern声明。声明。9.3 条件编译条件编译一般情况下,源程序中所有的行都参加编译。但是有时一般情况下,源程序中所有的行都参加编译。但是有时希望对其中一部分内容只在满足一定条件才进行编译,希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是也就是对一部分内容指定编译的条件,这就是“条件条件编译编译”。有时,希望当满足某条件时对一组语句进行。有时,希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。编译,而当条件不满足时则编译另一组语句。条件编译命令有以下几种形式:条件编译命令有
41、以下几种形式:(1)#ifdef标识符标识符程序段程序段1#else程序段程序段2#endif它的作用是当所指定的标识符已经被它的作用是当所指定的标识符已经被#define命令定命令定义过,则在程序编译阶段只编译程序段义过,则在程序编译阶段只编译程序段1,否则编,否则编译程序段译程序段2。其中。其中#else部分可以没有,即部分可以没有,即#ifdef标识符程序段标识符程序段1#endif这里的这里的“程序段程序段”可以是语可以是语句组,也可以是命令行。这种条件编译对于提高句组,也可以是命令行。这种条件编译对于提高C源程序的通用性是很有好处的。如果一个源程序的通用性是很有好处的。如果一个C源程
42、序源程序在不同计算机系统上运行,而不同的计算机又有在不同计算机系统上运行,而不同的计算机又有一定的差异一定的差异(例如,有的机器以例如,有的机器以16位位(2个字节个字节)来来存放一个整数,而有的则以存放一个整数,而有的则以32位存放一个整数位存放一个整数),这样往往需要对源程序作必要的修改,这就降低这样往往需要对源程序作必要的修改,这就降低了程序的通用性。可以用以下的条件编译来处理:了程序的通用性。可以用以下的条件编译来处理:#ifdefCOMPUTER-A#defineINTEGER-SIZE 16#else#defineINTEGER-SIZE 32#endif即如果即如果COMPUTE
43、R-A 在前面已被定义过,则编译在前面已被定义过,则编译下面的命令行:下面的命令行:#define INTEGER-SIZE 16否则,编译下面的命令行:否则,编译下面的命令行:#define INTEGER-SIZE 32如果在这组条件编译命令之前曾出现以下命令行:如果在这组条件编译命令之前曾出现以下命令行:#define COMPUTER-A 0或将或将COMPUTER-A定义为任何字符串,甚至是定义为任何字符串,甚至是#define COMPUTER-A则预编译后程序中的则预编译后程序中的INTEGER-SIZE都用都用16代替,否则都用代替,否则都用32代替。代替。这样,源程序可以不必
44、作任何修改就可以用于不同这样,源程序可以不必作任何修改就可以用于不同类型的计算机系统。当然以上介绍的只是一种简单类型的计算机系统。当然以上介绍的只是一种简单的情况,读者可以根据此思路设计出其他的条件编的情况,读者可以根据此思路设计出其他的条件编译。译。例如,在调试程序时,常常希望输出一些所需的信例如,在调试程序时,常常希望输出一些所需的信息,而在调试完成后不再输出这些信息。可以在源息,而在调试完成后不再输出这些信息。可以在源程序中插入以下的条件编译段:程序中插入以下的条件编译段:#ifdef DEBUGprintf(x=%D,y=%D,Z=%Dn,x,y,Z);#endif如果在它的前面有以下
45、命令行:如果在它的前面有以下命令行:#define DEBUG则在程序运行时输出则在程序运行时输出x、y、Z的值,以便调试时分析。的值,以便调试时分析。调试完成后只需将这个调试完成后只需将这个define命令行删去即可。有人命令行删去即可。有人可能觉得不用条件编译也可达此目的,即在调试时加可能觉得不用条件编译也可达此目的,即在调试时加一批一批printf语句,调试后一一将语句,调试后一一将printf语句删去。的确,语句删去。的确,这是可以的。但是,当调试时加的这是可以的。但是,当调试时加的printf语句比较多语句比较多时,修改的工作量是很大的。用条件编译,则不必一时,修改的工作量是很大的。
46、用条件编译,则不必一一删改一删改printf语句,只需删除前面的一条语句,只需删除前面的一条“#define DEBUG”命令即可,这时所有的用命令即可,这时所有的用DEBUG作标识符作标识符的条件编译段都使其中的的条件编译段都使其中的printf语句不起作用,即起语句不起作用,即起统一控制的作用,如同一个统一控制的作用,如同一个“开关开关”一样。一样。(2)#ifndef标识符标识符 程序段程序段1#else 程序段程序段2#endif只是第一行与第一种形式不同:将只是第一行与第一种形式不同:将“ifdef”改为改为“ifndef”。它的作用是若标识符未被定义过则编。它的作用是若标识符未被定
47、义过则编译程序段译程序段1,否则编译程序段,否则编译程序段2。这种形式与第一种。这种形式与第一种形式的作用相反。形式的作用相反。以上两种形式用法差不多,根据需要任选一种,视以上两种形式用法差不多,根据需要任选一种,视方便而定。例如,上面调试时输出信息的条件编方便而定。例如,上面调试时输出信息的条件编译段也可以改为译段也可以改为#ifndef RUNprintf(x=%D,y=%D,Z=%Dn,x,y,Z);#endif如果在此之前未对如果在此之前未对RUN定义,则输出定义,则输出x、y、Z的值。的值。调试完成后,在运行之前,加以下命令行:调试完成后,在运行之前,加以下命令行:#define R
48、UN则不再输出则不再输出x、y、Z的值。的值。(3)#if表达式表达式 程序段程序段1#else 程序段程序段2#endif它的作用是当指定的表达式值为真它的作用是当指定的表达式值为真(非零非零)时就编译时就编译程序段程序段1,否则编译程序段,否则编译程序段2。可以事先给定一定条。可以事先给定一定条件,使程序在不同的条件下执行不同的功能。件,使程序在不同的条件下执行不同的功能。例例9.7输入一行字母字符,根据需要设置条件编译,输入一行字母字符,根据需要设置条件编译,使之能将字母全改为大写输出,或全改为小写字母使之能将字母全改为大写输出,或全改为小写字母输出。输出。#define LETTER1
49、 main()char str20=CLanGuaGe,C;int i;i=0 while(C=stri)!=0)i+;#if LETTER 条件编译条件编译 if(C=a&C=Z)C=C-32;#elseif(C=A&C=Z)C=C+32;#endifprintf(%C,C);运行结果为:运行结果为:CLANGUAGE现在先定义现在先定义LETTER为为1,这样在对条件编译命,这样在对条件编译命令进行预处理时,由于令进行预处理时,由于LETTER为真为真(非零非零),则,则对第一个对第一个if语句进行编译,运行时使小写字母变大语句进行编译,运行时使小写字母变大写。如果将程序第一行改为写。如果
50、将程序第一行改为#defineLETTER0则在预则在预处理时,对第二个处理时,对第二个if语句进行编译处理,使大写字语句进行编译处理,使大写字母变成小写字母母变成小写字母(大写字母与相应的小写字母大写字母与相应的小写字母ASCII代码差代码差32)。此时运行情况为。此时运行情况为ClanGuaGe有的读者可能会问,不用条件编译命令而直接用有的读者可能会问,不用条件编译命令而直接用if语语句也能达到要求,用条件编译命令有什么好处呢句也能达到要求,用条件编译命令有什么好处呢?的确,此问题完全可以不用条件编译处理,但?的确,此问题完全可以不用条件编译处理,但那样做目标程序长那样做目标程序长(因为所