c语言基础教程第六章预处理功能和类型定义.docx
《c语言基础教程第六章预处理功能和类型定义.docx》由会员分享,可在线阅读,更多相关《c语言基础教程第六章预处理功能和类型定义.docx(25页珍藏版)》请在冰豆网上搜索。
![c语言基础教程第六章预处理功能和类型定义.docx](https://file1.bdocx.com/fileroot1/2023-5/31/187988da-d9fc-456d-8ae0-373c452ce4be/187988da-d9fc-456d-8ae0-373c452ce4be1.gif)
c语言基础教程第六章预处理功能和类型定义
第六章 预处理功能和类型定义
6.1预处理功能概述
本节主要讲述预处理功能的特点。
预处理功能是由很多预处理命令组成,这些命令将在编译时进行通常的编译功能〔包含词法和语法分析、代码生成、优化等〕之前进行处理,故称为"预处理".预处理后的结果和源程序一起再进行通常的编译操作,进而得到目标代码。
预处理功能主要包括如下三种;宏定义、文件包含和条件编译。
这些功能是通过相应的宏定义命令、文件包含命令和条件编译命令来实现。
这些命令不同于C语言的语句,因为它们具有如下的特点:
(1)多数预处理命令只是一种替代的功能,这种替代是简单的替换,而不进行语法检查。
(2)预处理命令都是在通常的编译之前进行的,编译时已经执行完了预处理命令,即对预处理后的结果进行编译,这时进行词法和语法分析等通常的程序编译。
(3)预处理命令后面不加分号,这也是在形式_七与语句的区别。
(4)为了使预处理命令与一般C语言语句相区别,凡是预处理命令都以井号("#")开头。
(5)多数预处理命令根据它的功能而被放在文件开头为宜,但是根据需要,也可以放到文件的其他位置。
不要产生错觉,好像所有的预处理命令都必须放在文件开头。
学习和掌握预处理功能时,应该了解它的上述规定,以便正确地使用和理解这些预处理命令。
6.2.1简单宏定义
1.简单宏定义的格式和功能
简单宏定义的格式如下:
#define<标识符)(字符串}
其中,define是关键字,它表示该命令为宏定义,悦标识符)是宏名,它的写法同标识符。
<字符串>用来表示<标识符>所代表的字符串。
简单宏定义是定义一个标识符(宏名)来代表一个字符串。
前面讲过的符号常量就是用这种简单宏定义来实现的。
例如:
#definePI3.14159265
这是一条宏定义的命令,它的作用是用指定的标识符PI来代替字符串"3.14159265".在程序中出现的是PI,在编译前预处理时,将所有的PI都用"3.14159265"来代替,即使用宏名来代替指定的字符串。
这一过程又称为"宏替换"或称为"宏展开".
[例6.1] 给出半径求圆的面积。
执行该程序,出现如下信息:
#dfinePI3.14159265
main()
{
fioatr,s,
Printf("Inputradius;")
scanf(""%f',&r);
A=PI*r*r
Printf(:
a=%.4f\n:
a);
}
执行该程序,出现如下信息:
Inputradius;5
输出结果如下
s=78.5398
说明:
该例中,开始率义了符号常量PI,它是用宏定义来实现的。
程序中出现的PI,在编译前预处理时将用"3.14159265"来替换。
2.使用简单宏定义时的注意事项
(1)宏定义中的<标识符)(即宏名)一般习惯用大写字母,以便与变量名区别。
这样。
在C语言程序的各表达式语句中,凡是大写字母的标识符(指全部大写字母)一般是符号常量。
但是,宏定义中的宏名也可以用小写字母。
(2)宏定义是预处理功能中的一种命令,它不是语句。
因此。
行末不需加分号。
如果加了分号,则该分号将作为所定义的字符串的一部分,即按字符串的一部分来处理。
(3)宏替换是一种简单的代替,替换时不作语法检查。
如果所定义的字符申中有错,例如,将数字。
,误写为字母。
,预处理照样代换,并不报错,而在编译中进行语法检查时才报错。
因此,要记住宏替换操作只是简单的代换,用宏定义时的字符串来替换其宏名。
(4)宏定义中宏名的作用域为定义该命令的文件中,并从定义时起,到终止宏定义命令(#undef<标识符))为止,如果没有终止宏定义命令,则到该文件结束为止。
通常放在文件开头,表示在此文件内有效。
终止宏定义命令的格式如下:
#undef(标识符)
其中,undef是关键字,(标识符)表示要终止的宏名,该宏名是在该文件中已定义的标识符。
例如:
#undefPI
表示宏定义的PI到此终止,即终止后的PI不再代表所定义的字符串了。
(5)宏定义可以嵌套。
所谓嵌套是指在进行宏定义时,可以引用已定义的宏名。
例如:
#defineWIDTH10
#defineLENGTH(WIDTH十10)
#defineAREA(LENGTH,WIDTH)
在第二个宏定义中引用了第一个宏定义的宏名WIDTH,而在第三个宏定义中引用了第一个宏定义的宏名WIDTH和第二个宏定义的宏名LENGTH,第二个和第三个宏定义便是宏定义的嵌套使用。
嵌套的宏定义在替换时,要进行层层替换。
例如,在上述宏定义的文件中,出现如下语句:
s=AREA+50;
则替换步骤如下:
先替换AREA,结果如下:
s=(LENGTH,WIDTH)十50,
再替换LENGTH,结果如下:
s=((WIDTH+10)*WIDTH)+50;
最后替换WIDTH,结果如下:
s=((10+IO),10)十50;
(6)一般编译系统对于加有双引号的字符串的宏名不予替换。
但是,有的编译系统对字符串的宏名也予替换。
使用时应该注意该编译系统对字符串内宏名的处理规则。
(7)一般编译系统在宏替换时,隐含一空格符,即用空格符将前后两部分分开。
有的编译系将不隐含空格符。
下面举几个例子,对宏定义的使用作进一步说明。
[例6.2] 分析下列程序输出结果,并说明简单宏定义在本程序中的应用。
#defineA一a
#defineTWOA2*A
main()
{
inta=1;
printf("TWOA="%d\nTWOA)。
printf("%d\n",-A);
}
执行该程序输出结果如下:
TWOA=-2
1
说明:
(1)该程序开头有两个宏定义命令,并且使用宏定义嵌套的方法。
(2)在TurboC编译系统中,对加双引号的字符串内的宏名不予替换'即printf()函数中。
控制串"TWOA=%d\n"中的TWOA不被替换,而参数TWOA被替换为
2*A
进一步替换为
2*一a
这里,a为1,上述结果为一2
又在第二个printf()函数中,其参数-A被替换为
一[」一a
其值为1.这里可以看出TurboC编译系统在宏替换时,加有隐含空格,其值才为to否则,替换后为
--a
其值为0.有的编译系统确有此种结果。
[例6.3] 分析下列程序的输出结果,并说明宏名的作用域。
main()
{
#defineN5
printf("N=%d\n,N);
#defineMN+3
printf("M=%d\n",M);
#undefN
#defineN1O
printf("newM=%d\n",M)}
}
执行该程序输出结果如下:
N=5
M=8
newM=13
说明:
(1)宏定义命令不一定必须写在文件开头,可以根据需要写在文件的任何位置。
该程序中在不同位置出现了3条宏定义命令和1条终止宏定义命令。
(2)宏名的作用域是在定义它的文件内,并从定义时开始,到终止定义时为止。
本例中,宏名N从定义时开始起作用,到#undefN命令为止。
本例中。
又再定义N到文件结束。
可见,对一个宏名进行重新定义之前必须先将原定义取消。
而本例中宏名M从定义时起作用,直到文件结束。
6.2.2带奋数的史定义
1.带参数宏定义的格式和功能
带参数宏定义的一般格式如下;
#define宏名>(<参数表>)<宏体>
其甲,<宏名>是标识符,一般习惯上用大写字母,<参数表>是由一个或多个参数组成的,多个参数之间用逗号分隔;(宏体)是一个字符串,其中包含<参数表>中所指定的参数,它可以是由若干个语句组成的。
该命令末尾一般不加分号。
带参数的宏定义在宏替换时。
不是简单的用宏体替换宏名,而是用"实参"替换"形参".这里所说的实参是指程序中引用宏名的参数,而形参是指宏定义时,宏名后边参数表中的参数。
例如,
#definesQ(x)x*x
在程序中出现下述语句:
A=SQ(5);
这里,在宏定义中参数x是形参,而程序中SQ(5)的S是实参,宏替换时,将用5来替换x,其结果如下:
a=5*5;
又例如,
#defineADD(x,y)x十Y
在程序中出现下述语句;
A=ADD(5,3);
宏替换后结果如下:
a=5十3;
如果在上述宏定义下,程序中出现如下语句;
b=ADD(a+1,b一1);
宏替换后结果如下:
B=a+1+b-1
由此可见,带参数的宏定义是这样替换的:
按照宏定义中所指定的宏体从左至右用程序中出现的宏的实参来替换宏体中的形参,对非参数字符,则保留。
宏中的实参可以是常量、变量或表达式。
2.使用带参数的宏定义应该注意的事项
(1)在宏定义时,宏名与左圆括号之间不能出现空格符,否则空格符后将作为宏体的一部分。
例如:
#defineADD(x,y)x十Y
将认为ADD是不带参数的宏名,而字符串(x,y)x+y作为宏体。
显然,这不是原来的含意。
因此,宏名后与左圆括号间一定不能加空格符。
(2)宏体中,各参数上加括号是十分重要的。
例如:
#defineSQ(x)x*x
当程序中出现下列语句,
A=SQ(a十1);
替换后,则为;
a=a+1*a+1
而不能将替换的结果写成
a=(a十1)*(a+1);
如果要将替换后结果写成上述形式,则需要将宏定义改写为:
#defineSQ(x)(x),(x)
由此可见,在宏定义中,对宏体内的形参外向加上括号是很重要的,它可以避免在优先级上可能出现的问题。
对上述宏定义最好写成下述形式:
#defineSQ(x)((.x)*(x))
这里的圆括号是很有用的。
例如,在有如下语句时,
m=50/SQ(b十1);
替换后结果如下:
m=50/(b十1)*(b+1));
(3)带参数的宏定义与函数的区别
带参数的宏定义和函数尽管在形式上非常相似,特别是当宏名使用小写字母时,出现在程序中很难区别出来是带参数的宏定义还是函数。
但是,这二者是根本不同,它们之间的区别概述如下:
①定义形式上不同。
带参数的宏定义的定义格式前面讲过了,它是通过预处理命令c}e-fine来定义的。
它的作用域是在定义它的文件内,并从定义处开始。
而函数的定义格式在本书"函数和存储类"1'章中描述过了。
它的作用域分为程序级的和文件级的两种。
②处理时间上不同。
宏定义是在编译预处理时处理的,处理后再进行编译。
而函数是在执行时处理的。
它们二者占用的是不同阶段的时间。
③处理方式_卜。
不同。
带参数的宏定义在进行宏替换时,用实参来代替形参,这里只是简单替换,并不做语法上的检查。
而函数调用时,是将实参的值赋给形参,要求对应类型一致。
宏定义中在参数替换时,不要求类型一致仁
④时间和空间的开销上不同。
带参数的宏定义,是在通常的编译之前完成替换的,因此它在该程序的目标代码的形成上并没有影响。
函数调用是在执行时进行的,因此,采用函数调用的方式可以减少该程序的目标代码,所以,在空间的开销上可以减少。
但是,函数调用要有额外的时间上的开销。
因为调用前要保留现场,调用后又要恢复现场,因此函数调用时间开销要比带参数的宏定义大。
带参数宏定义在使用中比函数时间开销小是一个重要特征。
⑤类型的要求上不同。
带参数的宏定义对形参的类型不必说明,它没有类型的限制。
而函数的形参在定义时必须进行类型说明。
例如,两个数相减的操作用带参数的宏定义和函数分别定义如下:
用带参数的宏定义格式如下:
抹defineMINU(x,y)(x)一(Y)
用函数的定义格式如下:
minu(x,y)
Intx,y
(
return(x一y);
}
该函数只能进行两个lnl型数值相减的运算。
而。
上述的宏定义可以进行两个。
har型量的相减,也可以进行两个int型数的相减。
还可以进行两'float型数的相减,因为它没有类型的限制通过上述对于带参数的宏定义和函数之间区别的分析,不难看出两者各有特点。
对于同一个间题一可以采用两种不同的表示形式,那么到底选择哪一种更好些呢?
一般说来,在功能比较简单的情况一下,选用带参数的玄定义能更好些,特别是在需要反复引用的情况下,用宏定义的时间开销较少。
例如,比较两个整数的大小,并输出最大的,可用如下的宏定义来实现:
#definefl(a,b)printf(%d\n",a>b?
;b);
又例如,计算一个自然数的立方值,可用如下的宏定义实现:
#definef2(a)printi<"%d\n,a*a*a);
功能比较复杂的还是选用函数来定义。
6.2.3宏定义的应用
在C语言程序中,宏定义主要用于下述几个方面。
(1)符号常耸的定义
C语言中的常觉一般都用符号常量表示,这样不仅书写简便,而且易于修改、易于移植,还可以使标识符有更明显的含意。
例如,
#definePI3.14159265
#defineE2.71828
#defineEPS1.0e一9
#define}MAX32767
#defineTRUE1
#defineFALSE0
等等。
(2)功能简单使用频繁的情况卜,用带参数的宏定义比用函数可以提高速度。
因为函数调用要花费额外的时间开销。
(3)在多数情况下,为了书写简练,程序易读而采用宏定义
[例6.4] 分析下列程序输出结果,并说明宏定义在该程序中所起的作用。
#defineF1"%d\n"
#defineF2"%d,%d\n"
#definePRl(a)print3(1,a)
#definePR2t(a.b)print(F2,a,b);
main)
{
intx+y,z;
x=5;
y=5*x;
z=x*y;
PR1(x);
PR2(y,z);
PR1(2,y);
PR2(x+5,z/5)
}
执行该程序输出的结果如下:
5
25.30
50
10.6
说明:
(1)该程序前面有4条宏定义命令。
前两条是简单宏定义,后两条是带参数的宏定义。
这4条宏定义命令都是有关标准文件的格式输出的。
(2)程序中的最后4条语句是输出显示结果的语句,由于引用了宏定义命令,使得该输出语句简练、清晰。
由于在C语言程序中,输出显示结果语句用得较多,并且可选用不同的参数个数,如果能使用宏定义命令使之简化,将给书写带来方便。
特别是这里使用一次文件包含命令(下面讲述)将宏定义部分写在一个。
h(头文件)文件内,程序需要时只要包含进去就可以了,这样将会更加简便。
6.3.1文件包含命令的格式和功能
文件包含命令是C语言程序常用的一条预处理命令,它的格式如下:
#include(文件名)
其中,include是关键字,(文件名)是被包含的文件名,这里要求使用文件全名,包括路径名和扩展名。
文件包含的功能就是将指定的被包含文件的内容放置在文件包含命令出现的地方。
该命令可写在程序的任一位置,但是一般写在一个文件的开头,这是一种明智的选择。
因为该命令在何处出现,所包含的文件内容就被放置到该命令出现的位置,这就相当于将被包含文件的内容插入到该命令的地方。
而被包含的。
h文件中往往是一些该程序所需要的一些说明或定义,它包括符号常量的定义、类型定义、带参数的宏定义、数组、结构、联合和枚举的定义等等,它还可以包含外部变量的定义、函数的定义和说明等等。
这些内容程序中将要用到,显然放在程序前面比放在中间和后面要好些。
文件包含的用途在于减少程序人员的重复劳动,使得C语言程序更加简洁,提高程序的可读性。
但是,如果对文件包含命令使用不当,会增加程序的代码长度,包含了一些该程序所不需要的内容。
因此,选择包含的文件时要慎重,定义被包含的文件时要短小。
[6.5]将例6.4中的宏定义部分写在一个print.h文件中,当程序需要时,将它包含进去。
其做法如下:
定义。
h文件名为print.h,它将包含有关输出显示结果的宏定义,其内容如下:
#defineF1'%d\n"
#defineF2"%d,I%d\n"
#de,inePR1(a)printf(Fl,a)
#definePR2(a,b)printf(F2,a,b)
程序内容如下
#include"print.h
Main()
{
intx,y,z;
X=5;
y=5*x:
z=x+y;
PR1(x);
PR2(y,Z);
PR12,Y);
PR2(x+5,z/5)
执行该程序的输出结果与例6.4的结果相同。
该程序中在开头使用了文件包含命令,将文件print.h包含在该程序的开头。
从程序书写角度来看只使用了一条文件包含命令便可代替该文件(指print.h文件)中的4条宏定义命令,可见书写简单了。
print.h文件还可以用于其他程序中,如果某个程序需要该文件的内容,只要使用一条文件包含命令就可以了。
6.3.2使用文件包含命令时应注意事项、
(1)一个文件包含命令只能包含一个文件。
如果某个程序需要包含多个文件,则可使用多条文件包含命令。
例如,下列写法是错误的,
#includ"xy.h""mn.h"
而改写成下列形式是正确的,
#include"xy.h"
#include"mn.h"
其前后顺序取决于被包含文件内容间的相互关系。
如果两个被包含文件内容相互无关,则书写的前后顺序也无关。
如果一个文件中要使用另一个文件的内容,则另一个文件要写在前面。
(2)文件包含的定义是可以嵌套的。
所谓文件包含定义的嵌套是指在一个被包含的文件中还可以包含有其他文件。
例如,
文件filel.c的内容如下:
#include"filet.h"
而被包含的文件#file2.h的内容如一下:
#include"file3.h"
这便是文件包含的嵌套,它可以等价于:
文件filetc的内容如下:
#include',file3.h"
#include''file2.h"
按上述顺序写比较安全。
(3)文件包含命令中,引用的被包含文件有下面两种方式:
方式一:
#include(文件名》
方式二:
#include"文件名,'
这两种方式中,文件名都要求是全名,方式一中用一对尖括号将文件名括起,它表示所引习的被包含文件是系统提供的,并放在指定目录下的。
h文件。
例如,
#include #include 等等。
这种方式引用时,系统将到指定的目录去查找系统提供的。
h文件。
方式二是用一对双引号将文件名括起,它表示所引用的被包含文件可能是用户自己定义的文件,并放在当前目录下或系统可自动查找的目录下。
对这种引用的文件,系统先在当前目录下或用nos命令((PATH)连起来的通用目录下查找,如果找不到再到系统指定的目录下查找带因此,要求编程人员对自己定义的被包含文件,引用时使用双引号,而系统提供的。
h文件最好使用尖括号,这样查找可能快些。
(4)被包含的文件可以是任意的源文件,不一定必须是。
h文件,也可以是。
c文件,
[例6.6]使用文件包含命令将一个。
c文件包含在另一个。
c文件之中。
f1.c文件内容如下:
add(x,y〕
intx,y;
{
intz;
z=x+y;
return(z);
{
f2.c文件内容如下:
#Include"'fl.c"
main()
{
inta,b;
a=5;
b=10;
printf("%d\n",add(a,})):
}
执行f2一文件后,将获得如h结果:
15
在f2.c文件的开头就使用文件包含命令将f1.c一文件包含了进去,这就相当于将fl.c文件的内容插入到f2.c文件中main()的前边。
(5)为提高程序的可移植性,编程时事先将一些在不同环境或条件厂需要修改的量或需要修改的部分放在一个被包含的文件中,以后为适应某种环境需要修改时。
可以只修改包含文件中的内容,这将为该程序的移植提供了方便。
6.4.1条件编译的常用命令格式
条件编译的常用命令格式有如下三种。
格式一:
#ifdef<标识符>
<程序段1>
#else
<程序段2>
#endif
其中,ifdef,else和endif是关键字。
(标识符少是指是否使用}defin命令定义过。
<程序段1>和<程序段2>是由语句或命令组成的程序段。
该格式的功能描述如下;
当<标识符>被宏定义时,<程序段1>中的语句或命令参加编译;否则<程序段2)中的语句或命令参加编译,该格式中,#else是可以省略的,则变成下列格式:
#ifdef<标识符>
(程序段)
#endif
格式二:
#ifndef<标识符>
<程序段1>
#else
<程序段2>
#endif
该格式与格式一大致相同,只是第一个关键字中不一样。
该格式的功能是:
如果<标识符>未被宏定义