C语言程序设计基础(第二版)课件:编译预处理与位运算-C语言的附加功能_第1页
C语言程序设计基础(第二版)课件:编译预处理与位运算-C语言的附加功能_第2页
C语言程序设计基础(第二版)课件:编译预处理与位运算-C语言的附加功能_第3页
C语言程序设计基础(第二版)课件:编译预处理与位运算-C语言的附加功能_第4页
C语言程序设计基础(第二版)课件:编译预处理与位运算-C语言的附加功能_第5页
已阅读5页,还剩46页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

编译预处理与位运算

——C语言的附加功能10.1编译预处理10.2位运算实操训练课外练习

10.1编译预处理

如何使用C语言系统提供的功能资源,方便用户编程?

在前面各章中,已多次使用过以“#”开头的预处理命令,如包含命令#include,宏定义命令#define等。在源程序中,这些命令都放在主函数之外,而且一般都放在源文件的开始位置,称为预处理部分。

所谓预处理,是指编译系统在对一个源程序进行编译之前,先对程序某些特殊命令进行处理,再将处理结果和源程序一起编译生成目标程序。预处理是C语言系统的一个重要功能,它由预处理程序来完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入源程序的编译。合理使用预处理功能可方便程序的编写、阅读、修改、移植和调试,能提高编程的效率,也有利于模块化程序设计。

C语言系统提供了多种预处理功能,如宏定义、文件包含、条件编译等。使用各种编译预处理命令时应注意以下几点:

(1)编译预处理命令不是C语言的语句,一般放在程序的开头,但也可以根据需要放在程序中间或程序末尾等位置。

(2)编译预处理命令的书写均以“#”开头,因为不是C语言的语句,所以末尾不加分号。

(3)一行只能书写一条编译预处理命令。

(4)编译预处理命令多用于实现一种简单替换功能,编译时系统不进行语法检查。

10.1.1宏定义

宏定义的作用是用标识符来代表一个字符串,即给字符串取名。一般是将一个复杂的字符串用一个简单的标识符代替,或将一个不便于理解和记忆的标识符用一个便于理解和记忆的标识符代替。可以说,宏定义的目的与意义是简化字符串或提高字符串的明晰性。编程时,可使用宏定义后的标识符代替被定义的字符串,C编译系统在编译之前又将这些标识符替换成被定义的字符串。宏定义分为不带参数的宏定义(即无参宏定义)和带参数的宏定义(即带参宏定义)。

1.无参宏定义

无参宏定义的一般形式为

#define标识符源字符串

其中,“标识符”为所定义的宏名;“源字符串”可以是常数、表达式、格式串等。

例如:

#definePI3.1415926

经此定义,程序就可以用PI代表3.141 592 6。显然,能简化书写,提高符号的明晰性。预编译时又要将源程序中所有宏名PI出现的位置用3.141 592 6来替换。

关于无参宏定义的几点说明:

(1)宏名一般用大写字母表示,以便与普通变量相区别。

(2) #与define间一般不留空格,宏名两侧必须至少用一个(可以多个)空格分隔。

(3)宏定义用宏名代替一个字符串,并不管它的数据类型是什么,也不管词法和语法是否正确,只作简单的替换。

(4) #define命令定义的宏名的作用范围是从定义命令开始,到源程序文件结束,一般情况下,#define总是定义在文件开头,不能在函数内。还可以在程序中通过#undef提前终止宏名的作用域。

(5)宏定义中,宏名还可以出现在被定义的字符串中,但还原时又分层置换。例如:

#definePI3.1415926

#defineSPI*y*y(PI是已定义的宏名)

此时,S表示的串是3.1415926*y*y。

(6)宏定义是专用于预处理的一个名词,它与变量定义的含义不同,只是在编译时进行的字符串的简单替换,不分配内存空间。它为编程提供了方便,能提高程序的通用性。

2.带参宏定义

C语言允许宏带参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。对带参数的宏,在调用中,不仅要求宏展开,而且要用实参去代换形参。

带参宏定义的一般形式为

#define宏名(形参表)字符串

其中,在字符串中含有形参。

带参宏调用的一般形式为

宏名(实参表);

例如:

#defineM(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)在带参宏定义中,形式参数不分配内存单元,因此不必作类型定义。而宏调用中的实参有具体的值,要用它们代换形参,因此必须作类型说明。这是与函数中的情况不同的。在函数中,形参和实参是两个不同的量,各有自己的作用域,调用时要把实参值赋予形参,进行“值传递”。而在带参宏中,只是符号代换,不存在值传递的问题。

(2)宏定义中的形参是标识符,宏调用中的实参可以是表达式。

(3)在宏定义中,字符串内的形参通常要用括号括起来,以避免出错。例10.1中的宏定义(y)*(y)表达式的y都用括号括起来。如果去掉括号,把预处理命令改为

#defineSQR(y)y*y

则宏代换后的语句是“q=a+1*a+1;”。显然将出现结果错误。

(4)函数调用和宏调用二者在形式上相似,但本质上是完全不同的。宏定义也可用来定义多个语句,在宏调用时,把这些语句又代换到源程序内。

10.1.2文件包含

文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行,作为本程序文件的组成部分。

关于文件包含命令的几点说明:

(1)包含命令中的文件名可以用双引号括起来,也可以用尖括号括起来,主要区别在于系统查找文件的路径不同。使用尖括号表示在包含文件目录中查找(包含目录是由用户在设置环境时设置的),而不在源文件目录中查找;使用双引号则表示首先在当前的源文件目录中查找,若未找到,再到包含目录中查找。这种使用形式更加通用。用户编程时可根据自己文件所在的目录来选择一种命令形式。

(2)一个include命令只能指定一个被包含文件,若有多个文件要包含,则需用多个include命令。文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。

10.1.3条件编译

预处理程序提供了条件编译的功能,可以按不同的条件编译不同的程序部分,因而产生不同的目标代码文件。这对于程序的移植和调试是很有用的。条件编译有三种形式。

上面介绍的条件编译当然也可以用条件语句来实现。但是用条件语句将会对整个源程序进行编译,生成的目标代码程序比较长,而采用条件编译,则根据条件只编译其中的程序段1或程序段2,生成的目标程序较短。如果条件选择的程序段很长,采用条件编译的方法是十分必要的。

10.2位运算

何谓位运算?C语言有哪些位运算功能?位运算就是对二进制数的按位运算。这种运算是在计算机的硬件级上常进行的运算。很多系统程序中常要求在位(bit)一级进行运算或处理。C语言提供了位运算的功能,这使得C语言既具有其他高级语言的特点,又具备低级程序设计语言的功能。位运算只能对整型或字符型数据进行。C语言中有6种位运算符,它们的作用、结合方向和优先级见表10.1。

10.2.1位运算符

1.位“与”运算

位“与”运算的符号为“&”,是对两个二进制数按位进行逻辑“与”运算。只有两个二进制数的运算位均为1时,该位的运算结果才为1,否则为0。

2.位“或”运算

位“或”运算的符号为“|”,是对两个二进制数按位进行逻辑“或”运算。只有两个二进制数的运算位均为0时,该位的运算结果才为0,否则为1。

按位“或”运算通常用来对某些位置1或保留某些位。例如a=a | 1,使a的最低位置1。

3.位“异或”运算

位“异或”运算的符号为“∧”,是对两个二进制数按位进行逻辑“异或”运算。两个二进制数的对应位相异时,结果为1;相同时,结果为0。

4.位“取反”运算

位“求反”运算的符号为“~”,是对一个二进制数逐位取反,即原为1,取反为0;原为0,取反为1。

5.左移运算

左移运算的符号为“<<”,是把一个二进制数左移指定的位数。左移时,高位丢掉,低位补0。要左移的数据对象在运算符左面,左移位数在运算符右面。

6.右移运算

右移运算的符号为“>>”,是把一个二进制数右移指定的位数。右移时高位补0,低位丢掉。要右移的数据对象在运算符左面,右移位数在运算符右面。

10.2.2位处理程序设计举例

在硬件接口上,常有输入状态端口的状态信息,可根据状态进行不同处理。例如,与打印机交换数据时要检测打印机的状态“BUSY”,如状态位为1,则等待,如为0则向打印机送数据。循环移位也是计算机系统常有的逻辑处理方式。下面针对这两种问题来说明位处理程序设计的方法。

例10.5输入一个十六进制数据和一个状态数据,检测状态数据的最低位是否为0,为0,则截取数据的4~7位输出,为1则输出“BUSY”。

编程思路:要检测某一位是否为0,只要设置一个对应位上为1、其他位上为0的二进制数,用这个二进制数和被检测的二进制数进行“与”运算,结果为0,则被检测位为0。要截取数据的4~7位,只要设置一个4~7位为1,其他位0的二进制数,与被截取的数据进行“与”运算,则数据的4~7位保留下来,其他数位均为0,然后右移4位,将保留数位移到最低位即可。

例10.6将a右循环移位k位。如果a中有二进制数1101111110101011,设右循环移位3位,则移位后的结果应为0111101111110101。

编程思路:C语言中的移位运算只能实现逻辑移位,不能实现循环移位,移位数据对象是变量。变量的值在计算机内是以二进制数存储的。要实现循环移位,必须采取一定算法。为满足题目要求,设字长为16位,可采用以下步骤:

(1)将a的低16-k位先逻辑左移到b的高位端,低16-k位全补为0,可用下面语句来实现:

b=a<<(16-k);

(2)将a逻辑右移k位,高k位补0,可用下面语句来实现:

c=a>>k;

10.2.3位段(位域)

如前所述,C语言的各种运算都是以字作为最基本单位进行的。但在某些特殊情况下,为节省存储空间,简化处理,允许信息存储时可以不占用一个完整的字存储单元,而只需占用几个二进制位。例如在存放一个开关量时,只有0和1两种状态,用一位二进制位即可。为此,C语言又提供了一种数据结构,称为位段或位域。

所谓位段,是把一个字单元中的二进制位划分为几个不同的区段,并说明每个区段的位数。每个区段有一个位段名,允许在程序中按位段名进行操作。这样就可以把几个不同的对象用一个字单元的几个二进制位段来表示。

位段定义就是把一个存储器字单元定义成一个位段结构体,一个位段是结构体中的一个成员。位段定义的一般形式为

其中,类型是指位段中数据的类型;位段名是位段引用的标识符;位数是位段所占的存储位,即二进制位数。位段、位数之和小于或等于系统字长。位段名可以没有,表示空位段。位段的使用采用引用结构体成员的方法。例如:

定义了3个有名位段a、b和c,分别占4位、3位和1位,存储无符号类型数。另外,包含一个无名位段,占4位,因为无名位段不能引用,所以相当于空位段,只占据位数。字长是32位的系统,定义位段后,字单元还剩余32-4-4-3-1=20(位),用“inti;”说明,表示可以存放整型数。若要给字段赋值,可用以下赋值语句:

关于位段定义和使用的几点说明:

(1)无名字段只用于填充或调整位置,是不能使用的。

(2)一个位段必须存储在一个单元中,不能跨两个单元。如果第一个单元不能容纳下一个位段,则从下一个单元起存放该位段。若某一位段要从另一个单元开始存放,则可以采用如下形式定义:

(3)位段可以在数值表达式中引用,系统自动转换成整型数。例如:

bit.a+5/bitdata.b

是正确的。

实操训练图10.1打印机状态寄存器

实训任务十学习位运算应用程序设计的方法实训项目打印机接口有一状态寄存器,其信息定义如图10.1所示。

设计程序,模拟读取状态寄存器,测试其状态,输出相应状态的信息。输入/输出界面可参照图10.2所示。图10.2实训项目界面式样

实训指导

1.设计程序

(1)定义一个短整型变量模拟状态寄存器,从键盘输入一个两位十六进制数(8位二进制数)来模拟读取状态寄存器的状态信息。

(2)通过位“与”运算,测试相关状态。设置某一状态对应位为1、其他位为0的二进制数,该数和状态寄存器内容进行位“与”运算,如果运算结果为0,则该位的状态值为0,否则该位状态值为1。

2.测试运行程序

从键盘分别输入反映打印机每一种状态的状态字,检测运行结果是否正确

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论