1、第十一章 位运算,基本内容,11.1 位运算和位运算符 11.2 位段(选讲),基本要求,掌握位运算的操作方法和概念。 掌握位运算符和表达式。 掌握位运算的作用,能够利用位运算进行一些简单的编程。,本章重点,重点:位运算符和表达式。 难点:位段。,本章作业,11.1 位运算符,语言提供了两类、六种位运算符:,& 按位与 | 按位或 按位异或 取反, 右移,位逻辑运算符,位移运算符,11.1 位运算符,说明,1. 只作用于整型或字符型的数据,不能为实型数据。 2. 作用于整型、字符型数据的每个二进制位,不是数的整体。 3. 一般逻辑运算是作用数的整体,不是数的每个二进制位,运算结果是二进制数。
2、4. 除“”以外,均为二目(元)运算符。,11.1 位运算符,按位与运算符(&),1. 运算规则:0&0=0 1&0=0 0&1=0 1&1=1 只要对应位上的值均为1则该位上的结果值为1。 2. 特殊作用:将一个存储单元各位清0。取某个数中的某些位。 方法:将本数与某个特定数按位与运算即可。,【例11.1】 main() int a=9,b=5,c;c=a ,11.1 位运算符,按位或运算符(|),1. 运算规则:0|0=0 1|0=1 0|1=1 1|1=1只要对应位上的值其中一个为1则该位上的结果值为1。 2. 特殊作用:常用于将一个数的某些特定位置为1 方法:将本数与某个特定数按位或运
3、算即可。,【例11.2】 main() int a=9,b=5,c;c=a|b; printf(“a=%dnb=%dnc=%dn“,a,b,c); ,11.1 位运算符,按位异或运算符(),1. 运算规则:00=0 10=1 01=1 11=0 只要对应位上的值互不相同则该位上的结果值为1。2. 特殊作用:使某些特定位翻转。任何数与0相异或结果保留原数本身。交换两个变量的值不用中间变量。a=ab; b=ba; a=ab; 注意给变量赋值的先后顺序。,【例11.3】 main() int a=9;a=a5;printf(“a=%dn“,a); ,11.1 位运算符,按位求反运算符(),1. 运算
4、规则:0=1 1=0 对每个上的值按位求反:1变为0;0变为1。 例如9的运算为:(0000000000001001)结果为:1111111111110110,11.1 位运算符,按位求反运算符(),注意:运算符比算术运算、关系运算、逻辑运算和其它运算的优先级别都高。例如:a&b先进行a运算,然后进行&运算。,11.1 位运算符,左位移运算符(),1. 运算规则: a=an 将a中所有位向左移动n位 2. 运算的作用:相当于乘法运算。左移一位相当于乘以2。 高位左边位左移后溢出被舍弃,不起作用。低位补以0。,11.1 位运算符,左位移运算符(),【注意】,11.1 位运算符,右位移运算符(),
5、1. 运算规则: a=an 将a中所有位向右移动n位。 2. 运算的作用相当于除法运算。右移一位相当于除以2。,11.1 位运算符,右位移运算符(),【注意】注意数的符号问题即正负的问题。 对于无符号数正数右移时高位补以0。 对于有符号数,高位为0正数右移时高位补以0。 对于有符号数,高位为1负数时:右移时高位补以0,称之为“逻辑位移”。右移时高位补以1,称之为“算术位移”。 TC采用“算术位移”,高位补以1。,11.1 位运算符,位运算赋值运算符,位运算符与赋值运算符可以组成复合赋值运算符如:&=, |=, =, =, = 例如:a & = b 相当于:a = a & ba =2 相当于:a
6、 = a 2,11.1 位运算符,不同长度的数据进行位运算,如果两个数据长度不同(例如long型和int型)进行位运算时(如a & b,而a为long型,b为int型),系统会将二者按右端对齐。 如果b为正数,则左侧16位补满0。若b为负数,左端应补满1。 如果b为无符号整数型,则左侧添满0。,【例11.4】取一个整数a从右端开始的47位。 可以这样考虑: 先使a右移4位,见图。图 (a)是未右移时的情况,图(b) 是右移4位后的情况。目的是使要取出的那几位移到最右端。,右移到右端可以用下面方法实现: a 4, 设置一个低4位全为1,其余全为0的数。可用下面方法实现: ( 0 4 ) 0的全部
7、二进制为全1,左移4位,这样右端低4位为0。见下面所示: 0:0000000000 0: 1111111111 04:1111110000 (04):0000001111, 将上面二者进行&运算。即 (a 4) & ( 0 4 ) 根据上一节介绍的方法,与低4位为1的数进行&运算,就能将这4位保留下来。,程序如下: main( )unsigned a,b,c,d;scanf(“%o“,&a);b=a4;c=(04);d=b&c;printf(“%o, %dn%o, %dn“,a,a,d,d); ,运行情况如下:331331, 217(a的值)15, 13 (d的值) 输入a的值为八进制数331
8、, 即十进制数217, 其二进制形式为11011001。经运算最后得到的d为00001101,即八进制数15,十进制数13。,如果任意指定从右面第m位开始取其右面n位。 只需将程序中的“b=a4”改成“b=a(m-n+1)”以及将“c=(04)”改成“c=(0n)”即可。,【例11.5】循环移位。要求将a进行右循环移位n位,见图。,即将a中原来左面(16-n)位右移n位,原来右端n位移到最左面n位。假设用两个字节存放一个整数。,为实现以上目的可以用以下步骤: 将a的右端n位先放到b中的高n位中。可以用下面语句实现:b=a(16-n); 将a右移n位,其左面高位n位补0。可以用下面语句实现:c=
9、an; 将c与b进行按位或运算。即c=c|b;,程序如下: main ( ) unsigned a,b,c;int n;scanf(“a=%o,n=%d“,&a,&n);b=a(16-n);c=an;c=c|b;printf(“%on%o“,a,c); ,运行情况如下: a=157653,n=315765375765 运行开始时输入八进制数157653,即二进制数1101111110101011,循环右移3位后得二进制数0111101111110101,即八进制数75765。 同样可以左循环位移。,11.2 位段,以前曾介绍过对内存中信息的存取一般以字节为单位。实际上,有时存储一个信息不必用一
10、个或多个字节,例如,“真”或“假”用0或1表示,只需1位即可。在计算机用于过程控制、参数检测或数据通信领域时,控制信息往往只占一个字节中的一个或几个二进位,常常在一个字节中放几个信息。那么,怎样向一个字节中的一个或几个二进位赋值和改变它的值呢?,11.2 位段,可以用以下两种方法:(1) 可以人为地在一个字节data中设几项。例如:a、b、c、d分别占2位、6位、4位、4位(见图)。,如果想将c的值变为12(设c原来为0),可以这样: 将数12左移4位,使1100成为右面起第47位。 将data与“124” 进行“按位或” 运算,即可使c的值变成12。 如果c的原值不为0,应先使之为0。可以用
11、下面方法:data=data & 0177417,0177417的二进制表示为: 11 11111 1 0000 1111 a b c d 也就是使第47位全为0,其他位全为1。它与data进行 &运算,使第47位为0,其余各位保留data的原状。这个177417称为“屏蔽字”,即把c以外的信息屏蔽起来,不受影响,只使c改变为0。,但要找出和记住177417这个数比较麻烦。 可以用data=data 15是c的最大值,c共占4位,最大值为1111即15。154是将1111移到47位。 再取反,就使47位变成0,其余位全是1。即 15:0000000000001111 15 4:00000000
12、11110000 ( 15 4 ):1111111100001111 这样可以实现对c清0,而不必计算屏蔽码。,将上面几步结合起来,可以得到 data=data n为应赋给c的值(例如12)。n & 15的作用是只取n的右端4位的值,其余各位置0,即把n放到最后4位上,( n & 15 ) 4, 就是将n置在47位上。见下面:,赋予47位为0,data & (154): 11011011|0000|1010 (n & 15)4: 00000000|1100|0000(按位或运算) 11011011|1100|1010 可见,data的其他位保留原状未改变,而第47位改变为12(即1100)了。
13、 但是用以上办法给一个字节中某几位赋值太麻烦了。可以用下面介绍的位段结构体的方法。,(2) 位段 C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域” ( bit field) 。利用位段能够用较少的位数存储数据。,例如:struct packed-dataunsigned a2;unsigned b6;unsigned c4;unsigned d4;int i;data; 其中a、b、c、d分别占2位、6位、4位、4位。i为整型。共占4个字节。,也可以使各个位段不恰好占满一个字节。如:struct packed-dataunsigned a
14、2;unsigned b3;unsigned c4;int i;struct packed-data data;,其中a、b、c共占9位,占1个字节多,不到2个字节。它的后面为int型,占2个字节。在a、b、c之后7位空间闲置不用,i从另一字节开头起存放。,注意,在存储单元中位段的空间分配方向,因机器而异。在微机使用的C系统中,一般是由右到左进行分配的,如图。但用户可以不必过问这种细节。,对位段中的数据引用的方法。如:data.a=2;data.b=7;data.c=9; 注意位段允许的最大值范围。 如果写dataa=8;就错了。因为data.a只占2位,最大值为3。在此情况下,自动取赋予它的
15、数的低位。例如,8的二进制数形式为1000,而dataa只有2位,取1000的低2位,故dataa得值0。,关于位段的定义和引用,有几点要说明: 位段成员的类型必须指定为unsigned或int类型。 若某一位段要从另一个字开始存放。可以用以下形式定义:unsigned a1;unsigned b2; 一个存储单元 unsigned0;unsigned c3; (另一存储单元),由于用了长度为0的位段,其作用是使下一个位段从下一个存储单元开始存放。,一个位段必须存储在同一存储单元中,不能跨两个单元。如果第一个单元空间不能容纳下一个位段,则该空间不用,而从下一个单元起存放该位段。可以定义无名字段
16、。如:unsigned a1;unsigned 2;(这两位空间不用)unsigned b3;unsigned c4; 在a后面的是无名位段,该空间不用。,位段的长度不能大于存储单元的长度,也不能定义位段数组。位段可以用整型格式符输出。如:printf(“%d,%d,%d“, data.a, data.b, data.c); 当然,也可以用%u、%o、%x等格式符输出。 位段可以在数值表达式中引用,它会被系统自动地转换成整型数。如:data.a+5/data.b,本章小结,位运算是语言的一种特殊运算功能,它是以二进制位为单位进行运算的。位运算符只有逻辑运算和移位运算两类。位运算符可以与赋值符一起组成复合赋值符。如&=,|=,=,=,=等。 利用位运算可以完成汇编语言的某些功能,如置位,位清零,移位等。还可进行数据的压缩存储和并行运算。,本章小结,位域在本质上也是结构类型,不过它的成员按二进制位分配内存。其定义、说明及使用的方式都与结构相同。 位域提供了一种手段,使得可在高级语言中实现数据的压缩,节省了存储空间,同时也提高了程序的效率。,