1、位运算 位域 联合 枚举Typedef预处理,第九章 其它C常用语法结构,9.1 位运算,数字计算机中使用晶体管存储和处理状态,它只有打开和关闭两种状态,因此非常适合用二进制数码的组合(即1和0)来表示信息。存储空间以字节为单位进行描述, 一个字节一般由8个二进制位(bit)组成。其中最右边的比特位称为“最低位”,最左边的比特位称为“最高位”。每一个二进制位的值要么取0,要么取1。位运算是指对数据的二进制位进行的操作。在C语言中,位运算符有位逻辑运算符和移位运算符。,位逻辑运算,位逻辑运算符主要是处理数据的二进制位, C语言中,位逻辑运算符有4个:(1)“按位与”运算符& “按位与”运算符&是
2、双目运算符,运算规则是:通过对两个操作数的补码逐位进行比较,只有两个对应位的二进制位都为1,则该位的结果才为1,否则为0。即:1 & 1= 1, 1 & 0= 0, 0 & 1= 0, 0 & 0= 0(2)“按位或”运算符| “按位或”运算符|是双目运算符,运算规则是:通过对两个操作数的补码逐位进行比较,两个对应的二进制位只要有一个为1,则该位的结果为1,否则为0。即:1 | 1 = 1, 0 | 1 = 1, 1 | 0 = 1, 0 | 0 = 0,位逻辑运算,(3)“按位异或”运算符 “按位异或”运算符是双目运算符,运算规则是:通过对两个操作数的补码逐位进行比较,如果两个相应的二进制位
3、相同,则该位的结果为0,否则为1。即:1 1 = 0,0 0 = 0,1 0 = 1,0 1 = 1(4)“按位取反”运算符 “按位取反”运算符是单目运算符,运算规则是:对操作数的补码每一位逐位取反,即该位为1,求反为0,反之亦然。即:1 = 0, 0 = 1,位逻辑运算优先级与结合性,位逻辑运算符的优先级基本处于关系运算符后面,逻辑运算符前面。但是单目运算符,处于较高的第二优先级,和+是同一级。其余3个运算符的优先级从高到低依次是&、|,处于关系运算符与逻辑运算符之间。它们容易混淆,所以使用时建议加上括号。例如:x = (i j)| (m & n)的结合性是从右到左,其他的三个运算符结合性是
4、从左到右。,移位运算符和移位表达式,在C中,移位运算符是指将位“左移”或“右移”。它们的操作数都应是整型量。由移位运算符和左右操作数构成的表达式称为移位表达式。(1)左移运算符右移运算符是双目运算符,运算规则是:将左侧操作数的值每位向右移动,移动的位数由右侧操作数决定。右侧移出的位被丢弃掉。对于unsigned类型,左侧空出的位填充0,对于有符号数,左侧空出的位依赖于机器,有些直接填充0,有些填充符号位。,移位运算符的优先级和结合性,两个移位运算符的优先级别相同,而且高于关系运算符,低于算术运算符,当然也高于位运算符。其结合性为从左至右。 移位运算符能够快速、高效的对2的幂做乘法或除法运算。
5、int i = 64; / 26=64,64的补码为0100 0000 i 2; /右移2位后为0001 0000,表达式值为16 i ”。如果直接对联合变量进行操作,使用“.”运算符;若联合变量通过一个指针来访问,则使用箭头运算符。例如,将整数100赋给value变量的成员num,可以用语句:value.num = 100;此时,联合变量value为一个整型变量。如果将字符A赋给value变量的成员ch,可以写成:value.ch = A;此时,联合变量value变为一个字符变量。 由于value是一个共享变量,因此在编程时必须记住在联合中当前存储的是什么样的数据类型。如果要进行数值计算,那
6、么用法要前后一致,即取回的类型一定是最近才存放的类型。如果以一种类型存放,而又以另一种类型取出,则可能发生错误。,9.4 枚举,通过列举一系列由用户自己指定的有序标识符而定义的类型叫枚举类型。标识符代表一个数据值,其间有先后次序,可以进行比较,通常把标识符称为枚举类型的元素。枚举类型在日常生活中十分常见,它采用比较接近人类自然语言的方式表示有关信息,以提高程序的可读性。比如,颜色有红、黄、绿、蓝等;每周的天数有星期日、星期一星期六;货币单位有分、角、元等;方向有上、下、左、右;一个学校的教师队伍由教授、副教授、讲师、助教组成等。,枚举的定义,枚举和结构一样,也是自定义的一种数据类型。枚举用关键
7、字enum表示,定义枚举型的一般形式为: enum 枚举类型名枚举表; 例如,下面程序段定义一个称为EnumClass的枚举类型,并说明col是属于这种类型的变量:enum EnumClassclass1, class2, class3, class4, class5; EnumClass col; 给出上述定义之后,枚举变量col可以取且只能取枚举表中某一个标识符。利用这个变量,下面的语句都是有效的。col = class1;if(col = class1) 处理class1的程序块;,枚举的应用,对枚举类型的定义及变量的使用,其实质是编译程序将枚举中的每个标识符按次序用它们所对应的整型数代
8、码来代替,在不进行初始化的情况下,第一个枚举标识符的代码值为0,第二个为1,依次类推。因此语句coutclass1class2class3class4class5endl;将会输出0 1 2 3 4 如果要改变缺省值,可以通过在标识符后加一个赋值号和一个整型量来实现。例如,定义:enum EnumClassclass1, class2, class3 = 5, class4, class5;则执行上面的输出语句后,系统将会输出:0 1 5 6 7即class3的值将为5,class4和class5的依次被改为6和7。,应用枚举的注意事项,枚举类型定义的存储特性与已经讨论过的变量的定义是一致的,
9、即在一个函数中定义的枚举型数据只限于在该函数中使用。而在程序中所有函数之外定义的外部枚举型数据可以为所有函数共用。使用枚举类型需注意以下问题: 枚举值是一个常量,不能在程序中对其重新赋值。 虽然每个枚举值都有对应的整数值,但不允许直接将整数值赋给枚举变量。如果希望将整数值赋给枚举变量,必须使用强制类型转换。 不能认为枚举变量与一个整数相加的结果是一个整数。所以,在上面对枚举类型EnumClass的值进行计数循环时,要使枚举变量classNum变为下一个枚举值,就不能直接使用classNum+,而是使用classNum = (EnumClass)(classNum + 1)来间接获得。,9.5生
10、成类型的别名:typedef,通过列举一系列由用户自己指定的有序标识符而定义的类型叫枚举类型。标识符代表一个数据值,其间有先后次序,可以进行比较,通常把标识符称为枚举类型的元素。枚举类型在日常生活中十分常见,它采用比较接近人类自然语言的方式表示有关信息,以提高程序的可读性。比如,颜色有红、黄、绿、蓝等;每周的天数有星期日、星期一星期六;货币单位有分、角、元等;方向有上、下、左、右;一个学校的教师队伍由教授、副教授、讲师、助教组成等。,typedef的定义,类型赋别名的一般形式为:typedef 类型 别名; 这里,类型是任一种合法的数据类型,别名是为这种类型新取的名字。此外,也可以用typed
11、ef为结构与联合等复杂的数据类型建立别名。例如:struct birth_date char name10; int month; int day; int year;typedef birth_date birthday;,typedef的定义(续),这里,把已定义的结构类型birth_date改为定义名birthday。利用别名birthday,也可以定义该结构类型的变量。 当要定义一个类型为birth_date,并含有100个元素的结构数组时,可表示成如下形式:birthday stuList100;上述的定义方式也可简写为如下形式:typedef struct char name10;
12、 int month; int day; int year;birthday;birthday student100;,typedef的优点,使用类型赋别名的好处很多,一是有时一个类型可以表示多个具有不同意义的对象,为了使对象的名字与其意义相符,我们可以使用类型赋别名建立原类型的别名,以后使用类型的别名定义变量,可以增加程序的可读性;二是有时一个类型在不同的C语言编译器中可能占用不同大小的内存空间,比如整型在VS2008中占用4字节,而在TC2.0中只占用2字节。如果程序必需使用4字节整数,为了增加程序的可移植性,我们可以将使用类型赋别名得到一个integer类型。例如,在VS2008中,将i
13、nt取名为integer,而在TC2.0中,将long取名为integer,这样就可以以最小的代价实现程序的可移值性。,typedef的示例,C语言允许用户使用关键字typedef,给已有的类型赋一个别名。例如:typedef int integer; 该语句使名字integer与标准类型int成为同义词。有了上述定义,类型integer就能用与int完全相同的方法进行使用。如可作下面的变量说明:integer x, y, width, longth; integer a10, *p;,typedef的注意事项,必须强调指出,typedef的作用,在任何意义上讲都不是创建一种新的数据类型,它仅
14、仅为现有的类型赋给一个新的名字,也没有任何新语义。因此,由定义名说明的变量的性质,与原有类型说明的变量的性质是相同的。即关键字typedef不能定义新的数据类型,但可以为已存在的数据类型生成一个新的名字。 使用typedef有助于增强程序的可移植性,也有助于增强程序的可读性。,9.6 预处理,通过预处理程序控制行能有效地扩充语言能力,是C语言的又一个重要特色。C语言预处理功能是由预处理程序实现的。C语言的预处理程序负责分析和处理以“#”为首字符的预处理控制行。预处理控制行主要有宏替换、文件包含和条件编译。由于预处理是在编译程序开始工作之前进行的,所以把执行预处理功能的这部分程序称为预处理程序。
15、从语法角度看,预处理控制行并不真正属于C语言的语句,它们可以出现在程序代码的任何地方,在它们的出现点后开始有效,通常宏替换和文件包含出现在文件的开头。预处理控制行的作用范围仅限于说明它们的文件,超出了那个文件就失去作用。恰当地使用C语言的预处理功能,可以扩展C语言程序的编程环境,可以编写出易读、易改、便于移植和调试的C程序,有利于实现符合软件工程要求的模块化程序设计。,宏替换 #define,1. 简单的字符串替换 宏替换命令#define用来定义一个宏标识符和一个字符串,在程序中每次遇到该标识符时,就用所定义的字符串替换,这个标识符也叫宏替换名,替换过程称为宏替换。宏替换命令的一般形式是:#
16、define 宏标识符 字符串 其中,宏标识符通常用大写字母表示,以便与程序中其它变量名相区别,明显地标示出程序中需要宏替换的地方,读程序时更加醒目。 #define、宏标识符、字符串各部分之间用空格分隔,其末尾不带分号,以换行结束。每行只能定义一个预处理行。例如:程序的开头, 如有以下宏替换命令#define N 10 在对该程序进行预处理时,凡程序中出现N的地方都用字符串10替换。,宏替换 #define,由于程序中多处用到N作为二维数组和一维数组的下标变量, for循环语句控制变量的终止值。因此,在程序调试过程中,将N定义为10,大大减少了数据的输入量,便于程序的查错修改和验证,节省了调
17、试程序的时间。 当程序调试完成后,若由于其它原因需要修改数组大小时,都只需要修改这条宏替换命令,再重新编译一次就可以了。这样对于修改、扩充、阅读和移植程序都是十分方便的。又如,在有布尔量的高级语言中,通常用“TRUE”和“FALSE”表示逻辑表达式运算的结果。在C语言中没有布尔量,所以用非零表示“TRUE,用零表示“FALSE。为了使这些代码的确切含义更为清楚,有利于阅读程序,通常可以使用两个宏替换#define进行定义:#define TRUE 1#define FALSE 0在数值计算中,如果要多次使用某些具有一定精度的float和double类型的数值,为了便于程序中使用,可作如下定义:
18、#define PI 3.14159#define E 2.7183#define INC 200.0,宏替换 #define,2. 带参宏定义及宏调用 宏替换命令#define还有一个重要的特点,即宏标识符像函数一样可以带有形式参数。在程序中用到宏标识符时,实际参数将代替这些形式参数,使用更为灵活。带参数宏定义的一般形式是:#define宏标识符(参数表) 表达式 宏标识符就是带参数宏的名字,参数表中的参数类似于函数中的形式参数;宏标识符与左圆括号之间不能有空格;表达式是用于替换的表达式。 宏调用的一般形式是:宏标识符(参数表) 此处的宏标识符是已被定义的宏标识符,参数表中的参数类似于函数中
19、的实参数。,应用场景,使用宏的主要优点是运行速度快,其定义在预处理阶段就被展开,省去了函数调用时所需的时间开销。但是,由于宏要在编译前被展开,宏替换会使程序代码长度增大,当宏在程序中出现的次数很多时更是如此。函数无论被调用多少次,它们在程序中只被定义一次,在目标程序中,只占用同样的一个存储空间。但参数的传递与返值、现场的恢复与保护都要占用时间,使程序运行速度变慢。因此,在程序设计中究竟使用带参数的宏好,还是函数好,应酌情而定。需要指出的是,在C标准程序库中,虽然大多数的例程都是由函数实现的,但有的例程是由“宏”实现的,而有的例程同时由函数和“宏”实现。 宏一旦被定义,在其所在的文件中均是存在和
20、可见的,这一点很像外部变量。如果要对某一个宏定义撤消,可用如下预处理命令:#undef 宏标识符 一旦消除了原来的定义,这个宏标识符可以重新被定义。,包含文件#include,C语言使用#include命令来处理 “包含文件”,用于将给定的包含文件的内容嵌入到另一个源程序文件中。其一般形式是:#include 文件名 或 #include 为了使编写的程序清晰易读,通常可以把常数和宏定义编到文件中,并通过#include预处理命令将其放入需要它的源文件中。文件也可用于包含外部变量和复杂数据类型的说明,所需要的类型仅需在include文件中定义和命名一次。 这种位于文件开始部分的被包含文件通常称
21、为“标题文件”或“头文件”,并以“.h”作为后缀,如C标准库函数中的各种“头文件”。 #include预处理命令的实质是,告诉预处理程序将包含文件的内容嵌入到源文件中#include出现的地方。,包含文件#include,需要嵌入的包含文件的存放地点,必须由文件的路径和文件名指定,并且被放在双引号内,系统约定先在引用包含文件的源文件所在的目录中查寻。若找不到,再按系统指定的标准方式向外查找。如果已经知道被包含文件不在当前工作目录,可以使用尖括号形式包含文件,它将只在标准目录中查找被包含文件。在前面所有例子中,使用的是标准目录中的头文件,所以使用尖括号。例如: 假设用户当前目录user中有一个表
22、示字形表的头文件font.h,C编译器的标准目录INCLUDE中也有一个同名的系统设置的字形表文件font.h。当用户程序需要使用自己编写的font.h文件时,应使用下面形式:#include font.h如果使用下面形式:#include 则C编译器会将系统设置的font.h文件而不是用户目录下的font.h文件嵌入用户程序。因此,在使用时,我们必须明确被包含文件是系统给定的,还是我们自己编写的,以便选用正确的引用方式。,包含文件#include,一个#include预处理命令只能指定一个被包含文件,如果要包含多个文件,就需要用多个包含文件预处理命令行。在使用多个包含文件时,#include
23、命令可以嵌套使用,即该预处理命令可以出现在另一个由#include指定的文件中。下面是一个学生信息管理系统的部分包含文件:#include #include #include login.h#include score.h#include “pick.h 其中,用尖括号括起的被包含文件是系统标准库函数的头文件;用双引号括起的被包含文件是学生信息管理系统自定义的头文件,它们包含了使用的常量、宏、外部变量及复杂数据类型的说明。在编译时,预处理程序把各个“.h”文件嵌入到上面指定的位置,使用括起的文件在标准目录中查找,使用括起的自定义文件则在源程序所在目录中查找。,本章小结,1. C语言定义了位运算
24、,因此可以直接对底层硬件进行操作。在单片机、嵌入式系统的编程中,我们经常要使用此运算符。2. 位域结构是结构的一种特殊形式。位域的长度是以二进制位为单位定义的,其成员的数据类型只能是整型和字符型。以二进制位为长度单位对位域成员变量的访问,给按位处理数据带来了极大的方便。此外,使用此类型还可以减少内存占用。3. 联合是多个不同类型的变量共用同一内存空间的共享体,它与结构的主要区别是:由于联合各成员共享一个公共存储空间,因此在任何给定的时刻,只能允许一个成员占据联合变量的空间。使用时要注意存入和引用的一致性,即占用当前联合变量空间的是哪个成员,引用时只能引用该成员或可替换的成员,否则会出现错误。应
25、用联合变量各成员共享存储空间的特点,有利于数据的交换和处理。4. 联合变量与结构变量的主要相同点是:类型定义和变量定义的形式相同;成员变量的引用方法相同;变量的生命期和作用域相同。结构和联合可以互相嵌套,以表示更为复杂的数据结构。,本章小结(续),5. 枚举类型是自定义的有序标识符表。枚举类型和枚举变量的定义,与结构类型和结构变量的定义相似。对枚举类型的定义及变量的使用,其实质是编译程序将枚举中的每个枚举元素,按序用其对应的整型数值来代替。 6. 使用typedef可以对已有的数据类型产生一个新的名字,可以使程序简洁,还可用于提高程序的可移植性,但它并不是定义一种新的数据类型。7. C语言的预处理功能是由预处理程序实现的。预处理命令行都要以“#”开始,可以出现在源程序文件中的任何地方,通常把宏定义和文件包含放在文件的开头。8. 宏定义中的简单字符串替换用于定义符号常量。带参数宏定义与宏调用,与函数定义和函数调用类似。但宏的参数不存在数据类型,可适用于任何类型参数。9. 包含文件是使用#include将要包含的文件插入到该命令行的相应位置处。被包含的文件名必须用双引号或尖括号括起来。10. 条件编译是按条件(分别按表达式值、标识符是否被定义或标识符是否未被定义3种情况)有选择地编译某个程序段。,结束页,今天就讲到这里,再见!,