1、第10章 编译预处理与位运算C语言的附加功能10.1 编译预处理编译预处理10.2 位运算位运算实操训练实操训练课外练习课外练习10.1 编译预处理编译预处理如何使用如何使用C语言系统提供的功能资源,方便用户编程?语言系统提供的功能资源,方便用户编程?在前面各章中,已多次使用过以“#”开头的预处理命令,如包含命令#include,宏定义命令#define等。在源程序中,这些命令都放在主函数之外,而且一般都放在源文件的开始位置,称为预处理部分。所谓预处理,是指编译系统在对一个源程序进行编译之前,先对程序某些特殊命令进行处理,再将处理结果和源程序一起编译生成目标程序。预处理是C语言系统的一个重要功
2、能,它由预处理程序来完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入源程序的编译。合理使用预处理功能可方便程序的编写、阅读、修改、移植和调试,能提高编程的效率,也有利于模块化程序设计。C语言系统提供了多种预处理功能,如宏定义、文件包含、条件编译等。使用各种编译预处理命令时应注意以下几点:(1)编译预处理命令不是C语言的语句,一般放在程序的开头,但也可以根据需要放在程序中间或程序末尾等位置。(2)编译预处理命令的书写均以“#”开头,因为不是C语言的语句,所以末尾不加分号。(3)一行只能书写一条编译预处理命令。(4)编译预处理命令多用于实现一种
3、简单替换功能,编译时系统不进行语法检查。10.1.1 宏定义宏定义宏定义的作用是用标识符来代表一个字符串,即给字符串取名。一般是将一个复杂的字符串用一个简单的标识符代替,或将一个不便于理解和记忆的标识符用一个便于理解和记忆的标识符代替。可以说,宏定义的目的与意义是简化字符串或提高字符串的明晰性。编程时,可使用宏定义后的标识符代替被定义的字符串,C编译系统在编译之前又将这些标识符替换成被定义的字符串。宏定义分为不带参数的宏定义(即无参宏定义)和带参数的宏定义(即带参宏定义)。1无参宏定义无参宏定义无参宏定义的一般形式为#define 标识符 源字符串其中,“标识符”为所定义的宏名;“源字符串”可
4、以是常数、表达式、格式串等。例如:#define PI 3.1415926经此定义,程序就可以用PI代表3.1415926。显然,能简化书写,提高符号的明晰性。预编译时又要将源程序中所有宏名PI出现的位置用3.1415926来替换。关于无参宏定义的几点说明:(1)宏名一般用大写字母表示,以便与普通变量相区别。(2)#与define间一般不留空格,宏名两侧必须至少用一个(可以多个)空格分隔。(3)宏定义用宏名代替一个字符串,并不管它的数据类型是什么,也不管词法和语法是否正确,只作简单的替换。(4)#define命令定义的宏名的作用范围是从定义命令开始,到源程序文件结束,一般情况下,#define
5、总是定义在文件开头,不能在函数内。还可以在程序中通过#undef提前终止宏名的作用域。(5)宏定义中,宏名还可以出现在被定义的字符串中,但还原时又分层置换。例如:#define PI 3.1415926#define S PI*y*y (PI是已定义的宏名)此时,S表示的串是3.1415926*y*y。(6)宏定义是专用于预处理的一个名词,它与变量定义的含义不同,只是在编译时进行的字符串的简单替换,不分配内存空间。它为编程提供了方便,能提高程序的通用性。2带参宏定义带参宏定义C语言允许宏带参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要求宏展开,
6、而且要用实参去代换形参。带参宏定义的一般形式为#define 宏名(形参表)字符串其中,在字符串中含有形参。带参宏调用的一般形式为宏名(实参表);例如:#define M(y)y*y+3*y /宏定义,y是形参k=M(5);/宏调用,5是实参定义了带参数的宏M(y),y为宏的形参,即用M(y)来代替y*y+3*y。M(5)是宏调用,宏调用时,用实参5代替形参y。经预处理,宏展开后的语句为k=5*5+3*5;关于带参宏定义的几点说明:(1)在带参宏定义中,形式参数不分配内存单元,因此不必作类型定义。而宏调用中的实参有具体的值,要用它们代换形参,因此必须作类型说明。这是与函数中的情况不同的。在函数
7、中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行“值传递”。而在带参宏中,只是符号代换,不存在值传递的问题。(2)宏定义中的形参是标识符,宏调用中的实参可以是表达式。(3)在宏定义中,字符串内的形参通常要用括号括起来,以避免出错。例10.1中的宏定义(y)*(y)表达式的y都用括号括起来。如果去掉括号,把预处理命令改为#define SQR(y)y*y则宏代换后的语句是“q=a+1*a+1;”。显然将出现结果错误。(4)函数调用和宏调用二者在形式上相似,但本质上是完全不同的。宏定义也可用来定义多个语句,在宏调用时,把这些语句又代换到源程序内。10.1.2 文件包含
8、文件包含文件包含命令的功能是把指定的文件插入该命令行位文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行,作为本程序文件的组成部分。置取代该命令行,作为本程序文件的组成部分。关于文件包含命令的几点说明:(1)包含命令中的文件名可以用双引号括起来,也可以用尖括号括起来,主要区别在于系统查找文件的路径不同。使用尖括号表示在包含文件目录中查找(包含目录是由用户在设置环境时设置的),而不在源文件目录中查找;使用双引号则表示首先在当前的源文件目录中查找,若未找到,再到包含目录中查找。这种使用形式更加通用。用户编程时可根据自己文件所在的目录来选择一种命令形式。(2)一个include命令只能指定
9、一个被包含文件,若有多个文件要包含,则需用多个include命令。文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。10.1.3 条件编译条件编译预处理程序提供了条件编译的功能,可以按不同的条件编译不同的程序部分,因而产生不同的目标代码文件。这对于程序的移植和调试是很有用的。条件编译有三种形式。上面介绍的条件编译当然也可以用条件语句来实现。但是用条件语句将会对整个源程序进行编译,生成的目标代码程序比较长,而采用条件编译,则根据条件只编译其中的程序段1或程序段2,生成的目标程序较短。如果条件选择的程序段很长,采用条件编译的方法是十分必要的。10.2 位位 运运 算算何谓位运算?何谓位
10、运算?C语言有哪些位运算功能?语言有哪些位运算功能?位运算就是对二进制数的按位运算。这种运算是在计算机的硬件级上常进行的运算。很多系统程序中常要求在位(bit)一级进行运算或处理。C语言提供了位运算的功能,这使得C语言既具有其他高级语言的特点,又具备低级程序设计语言的功能。位运算只能对整型或字符型数据进行。C语言中有6种位运算符,它们的作用、结合方向和优先级见表10.1。10.2.1 位运算符位运算符1位位“与与”运算运算位“与”运算的符号为“&”,是对两个二进制数按位进行逻辑“与”运算。只有两个二进制数的运算位均为1时,该位的运算结果才为1,否则为0。2位位“或或”运算运算位“或”运算的符号
11、为“|”,是对两个二进制数按位进行逻辑“或”运算。只有两个二进制数的运算位均为0时,该位的运算结果才为0,否则为1。按位“或”运算通常用来对某些位置1或保留某些位。例如a=a|1,使a的最低位置1。3位位“异或异或”运算运算位“异或”运算的符号为“”,是对两个二进制数按位进行逻辑“异或”运算。两个二进制数的对应位相异时,结果为1;相同时,结果为0。4位位“取反取反”运算运算位“求反”运算的符号为“”,是对一个二进制数逐位取反,即原为1,取反为0;原为0,取反为1。5左移运算左移运算左移运算的符号为“”,是把一个二进制数右移指定的位数。右移时高位补0,低位丢掉。要右移的数据对象在运算符左面,右移
12、位数在运算符右面。10.2.2 位处理程序设计举例位处理程序设计举例在硬件接口上,常有输入状态端口的状态信息,可根据状态进行不同处理。例如,与打印机交换数据时要检测打印机的状态“BUSY”,如状态位为1,则等待,如为0则向打印机送数据。循环移位也是计算机系统常有的逻辑处理方式。下面针对这两种问题来说明位处理程序设计的方法。例例10.5 输入一个十六进制数据和一个状态数据,检测状态数据的最低位是否为0,为0,则截取数据的47位输出,为1则输出“BUSY”。编程思路:要检测某一位是否为0,只要设置一个对应位上为1、其他位上为0的二进制数,用这个二进制数和被检测的二进制数进行“与”运算,结果为0,则
13、被检测位为0。要截取数据的47位,只要设置一个47位为1,其他位0的二进制数,与被截取的数据进行“与”运算,则数据的47位保留下来,其他数位均为0,然后右移4位,将保留数位移到最低位即可。例10.6 将a右循环移位k位。如果a中有二进制数1101111110101011,设右循环移位3位,则移位后的结果应为0111101111110101。编程思路:C语言中的移位运算只能实现逻辑移位,不能实现循环移位,移位数据对象是变量。变量的值在计算机内是以二进制数存储的。要实现循环移位,必须采取一定算法。为满足题目要求,设字长为16位,可采用以下步骤:(1)将a的低16-k位先逻辑左移到b的高位端,低16
14、-k位全补为0,可用下面语句来实现:b=ak;10.2.3 位段位段(位域位域)如前所述,C语言的各种运算都是以字作为最基本单位进行的。但在某些特殊情况下,为节省存储空间,简化处理,允许信息存储时可以不占用一个完整的字存储单元,而只需占用几个二进制位。例如在存放一个开关量时,只有0和1两种状态,用一位二进制位即可。为此,C语言又提供了一种数据结构,称为位段或位域。所谓位段,是把一个字单元中的二进制位划分为几个不同的区段,并说明每个区段的位数。每个区段有一个位段名,允许在程序中按位段名进行操作。这样就可以把几个不同的对象用一个字单元的几个二进制位段来表示。位段定义就是把一个存储器字单元定义成一个
15、位段结构体,一个位段是结构体中的一个成员。位段定义的一般形式为其中,类型是指位段中数据的类型;位段名是位段引用的标识符;位数是位段所占的存储位,即二进制位数。位段、位数之和小于或等于系统字长。位段名可以没有,表示空位段。位段的使用采用引用结构体成员的方法。例如:定义了3个有名位段a、b和c,分别占4位、3位和1位,存储无符号类型数。另外,包含一个无名位段,占4位,因为无名位段不能引用,所以相当于空位段,只占据位数。字长是32位的系统,定义位段后,字单元还剩余32-4-4-3-1=20(位),用“int i;”说明,表示可以存放整型数。若要给字段赋值,可用以下赋值语句:关于位段定义和使用的几点说
16、明:(1)无名字段只用于填充或调整位置,是不能使用的。(2)一个位段必须存储在一个单元中,不能跨两个单元。如果第一个单元不能容纳下一个位段,则从下一个单元起存放该位段。若某一位段要从另一个单元开始存放,则可以采用如下形式定义:(3)位段可以在数值表达式中引用,系统自动转换成整型数。例如:bit.a+5/bitdata.b是正确的。实操训练实操训练图10.1 打印机状态寄存器实训任务十 学习位运算应用程序设计的方法实训项目 打印机接口有一状态寄存器,其信息定义如图10.1所示。设计程序,模拟读取状态寄存器,测试其状态,输出相应状态的信息。输入/输出界面可参照图10.2所示。图10.2 实训项目界
17、面式样实训指导实训指导1设计程序(1)定义一个短整型变量模拟状态寄存器,从键盘输入一个两位十六进制数(8位二进制数)来模拟读取状态寄存器的状态信息。(2)通过位“与”运算,测试相关状态。设置某一状态对应位为1、其他位为0的二进制数,该数和状态寄存器内容进行位“与”运算,如果运算结果为0,则该位的状态值为0,否则该位状态值为1。2测试运行程序从键盘分别输入反映打印机每一种状态的状态字,检测运行结果是否正确。状态字设置如表10.2所示。课外练习课外练习1从每小题的从每小题的4个备选项中选择一个正确项。个备选项中选择一个正确项。(1)下列程序执行后的输出结果是()。#define MA(x)x*(x
18、-1)#include main()int a=1,b=2;printf(%d n,MA(1+a+b);A6B8C10D12(2)若有宏定义#define MOD(x,y)x%y,则执行以下语句后的输出为()。int z,a=15,b=100;z=MOD(b,a);printf(%dn,z+);A11B10C6D宏定义不合法(3)以下程序运行后,输出结果是()。#define PT55#define S(x)PT*x*x#include main()inta=1,b=2;printf(%4.1fn,S(a+b);A49.5B9.5C22.0D45.0 2分析下面程序的功能及运行结果,并上机验证。
侵权处理QQ:3464097650--上传资料QQ:3464097650
【声明】本站为“文档C2C交易模式”,即用户上传的文档直接卖给(下载)用户,本站只是网络空间服务平台,本站所有原创文档下载所得归上传人所有,如您发现上传作品侵犯了您的版权,请立刻联系我们并提供证据,我们将在3个工作日内予以改正。