第八章 位运算
概述
所谓位运算是指进行二进制位的运算,在系统软件中,常要
处理二进位的问题,例如,将一个存储单元中的各二进位左移
或右移一位,两个数按位相加等,
一,字节和位
大多计算机系统的内存储器是由许多称为“字节” (byte)
的单元组成的,每一个字节有一个地址,一个字节由若干个二
进制位 (bit)组成,若干个字节组成一个存储单元,称为,字
”(word).每一个存储单元存放一个数据或一条指令,
一个字节一般由 8个二进制位组成,其中最右边的一位称为
“最低有效位”或“最低位”,最左面的一位称为“最高有效
位”或“最高位”,每一个二进位的值是 0或 1,
在微型机中一般以 4个字节存放一个实数,以 2个字节存放
一个整数,最左边的一位 (最高位 )用作数的符号位,
为了表示数值,可以采用不同的方法,一般有,原码,反码和补码,
位运算符
运算符 含义
& 按位与
| 按位或
^ 按位异或
~ 取反
<< 左移
>> 右移
C语言提供如下表所列出的位运算符,
1.位运算符中除 ~以外,均为二目 (元 )运算符,即要求两侧
各有一个运算符,
2.运算量只能是整型或字符型的数据,不能为实型数据,


运算符简介
一,“按位与”运算符 (&)
参加运算的两个运算量,如果有个相应的位都为 1,则该位
的结果值为 1,否则为 0.即,
0&0=0; 0&1=0; 1&0=0; 1&1=1;
例如, 3&5并不等于 8,这是按位与,先把 3和 5以补码表示,再
进行按位与运算,
3的补码, 00000011
5的补码, 00000101
&,00000001
它是 1的补码,因此,3&5的值得 1.——例 L8-1
按位与的特殊用途
1.清零,如果想将一个单元清零,即使其全部二进位为 0,只
要找一个数,它的补码形式中各位的值符合以下条件,原来
的数中为 1的位,新数中相应位为 0.然后使二者进行 &运算,
如, 原有数为 00101011,另找一个数,设它为 10010100,它符
合以上条件,即在原数为 1的位置上,它的位值均为 0.将两
个数进行 &运算,
00101011 ( 43)
& 10010100 ( 148)
00000000 ( 0)
其道理是显然的,当然也可以不用 10010100这个数而用其
它数 (如 01000100)也可以,只要符合上述条件即可,
例 L8-2
2.取一个数中某些指定位,如一个整数 a(2个字节 ),如只想要
其中的低字节,只需将 a与 (377)8按位与即可,见下图,
00 10 11 00 10 10 11 00
00 00 00 00 11 11 11 11
00 00 00 00 10 10 11 00
a
b
c
c=a&b,b为八进制的 377,c只取 a的低字节,高字节为 0,
如果想取两个字节中的高字节,只需,c=a&0177400 (0177400表
示八进制的 177400).见下图,
00 10 11 00 10 10 11 00
11 11 11 11 00 00 00 00
00 10 11 00 00 00 00 00
a
b
c
例 L8-3
11436
255
172
11436
11264
65280
3.要想将哪一位保留下来,就与一个数进行 &运算,此数在
该位取 1,如, 有一数 01 0101 00,想把其第 2,3,4位保留下来,
可以这样
01010100 (十进制数 84)
(&) 00011100 (十进制数 28)
00010100 (十进制数 20)
即, a =84,b=28,c=a & b=20
例 L8-4
二,按位或运算符 ( | )
两个相应位中只要有一个为 1,该位的结果值为 1.即,
0 | 0=0; 0 | 1=1; 1 | 0=1; 1 | 1=1,
例如, 060 | 017
将八进制数 60与八进制数 17进行按位或运算,
00110000
( | ) 00001111
0011 1111 ( 63) 10 例 L8-5
把低 4位全置 1.如果想使一个数 a的低 4位改为 1,只需将 a与
017进行按位或运算即可,
按位或运算常用来对一个数据的某些位定值为 1.如, a是
一个整数 (16位 ),有表达式 a | 0377
则低 8位全置为 1.高 8位保留原样,
三,“异或”运算符 ( ^ ),也称 XOR运算符
它的规则是, 参加运算的两个相应位同号,则结果为 0(假 );异
号则为 1(真 ).即,
0 ^ 0=0; 0 ^ 1=1; 1 ^ 0=1; 1 ^ 1=0;
如,
00111001 (十进制数 57,八进制数 071)
( ^ ) 00101010 (十进制数 42,八进制数 052)
00010011 (十进制数 19,八进制数 023)
即 071 ^ 052,结果为 023(八进制数 ),例 L8-6
“异或”的意思是,判断两个相应的位值是否为“异”,为
“异” (值不同 )就取真 (1),否则为假 (0),
四,“取反”运算符 ( ~ )
~是一个单目 (元 )运算符,用来对一个二进制数按位取反,即
将 0变 1,1变 0.例如,
~025是对八进制数 25(即二进制数 0000000000010101)按位求反,
0000000000010101
1111111111101010
即八进制数 177752.因此,~025的值为八进制数 177752.不要以为
~025的值是 -025,
~运算符的应用,
五,左移运算符 ( << )
用来将一个数的各二进位全部左移若干位,例如,
a=a<<2
将 a的二进制数左移 2位,右补 2位,右补 0.若 a=15,即二进制数
00001111,左移 2位得 00 11 11 00,即十进制数 60,例 L8-7
高位左移后溢出,舍弃不起作用,
左移 1位相当于该数乘以 2,左移 2位相当于该数乘以 22=4.
上面举的例子 15<<2=60,即乘了 4.但此结论只适用于该数左移
时被溢出舍弃的高位中不包含 1的情况,例如,假设以一个字节
(8位 )存一个整数,若 a为无符号整型变量,则 a=64时,左移一位
时溢出的是 0,而左移 2位时,溢出的高位中包含 1.( 同理,乘 2
相当于左移一位 )
a的值 a的补码形式 a<<1 a<<2
64 01000000 0...10000000 01...00000000
127 01111111 0...11111110 01...11111100
六,右移运算符 ( >> )
a>>2表示将 a的各二进位右移 2位,移到右端的低位被舍弃,
对无符号数,高位补 0.如 a=017时, 例 L8-8
a为 00001111,a>>2为 00000011...11
此二位位舍弃
右移一位相当于除以 2,右移 n位相当于除以 2n,
在右移时,需要注意符号位问题,对无符号数,右移时左边高
位移入 0.对于有符号的值,如果原来符号位为 0(该数为正 ),
则左边也是移入 0,如同上例表示的那样,如果符号位原来为
1(即负数 ),则左边移入 0还是 1,要取决于所用的计算机系统,
有的系统移入 0,有的称入 1.移入 0的称为,逻辑右移,,即简单
右移,移入 1的称为,算术右移,,
TC中为算术左移。 例 L8-9
位运算举例
例 L8-10 将 16进制化为 2进制。
例 L8-10-2 按位运算计算器。
例题分析
例 L8-11, 循环移位,要求将 a进行右循环移位,如下图,
a,
c,
n位
n位 步骤如下,
(1)将 a的右端 n位先放到 b中的高 n位中,可以用下面语句实现,
b=a<<(16-n);
(2)将 a右移 n位,其左面高位 n位补 0.可以用下面语句实现,
c=a>>n;
(3)将 c与 b进行按位或运算,即
c=c | b;
程序如下,
main()
{ unsigned a,b,c ; int n;
scanf(“a=%o,n=%d”,&a,&n);
b=a<<(16-n);
c=a>>n;
c=c | b;
printf(“%o\n%o”,a,c);
}
例 L8-11-2, 循环移位,要求用函数完成,
位段
有时存储一个信息不必用一个或多个字节,要以在一个字
节中放几个信息,例如,“真”或“假”用 0或 1表示,只需 1位即
可,可以有以下两个办法,
一,人为地在一个字节中设几项,例如, a,b,c,d分别占 2位,6位,4
位,4位,如下图
a b c d data,
15 14 13 8 7 4 3 0
如果想将改变 c的值,可以用前面学过的方法解决,但太麻
烦了。
二,位段
所谓位段是以位为单位定义长度的结构体类型中的成员,
例如,
struct packed_data
{ unsigned a:2;
unsigned b:6;
unsigned c:4;
unsigned d:4;
int i;
}data;
如下图,其中 a,b,c,d分别占 2位,6位,4位,4位,i为整型,共占 4个字
节,
2 6 4 4 16
a b c d i
也可以使各个位段不恰好占满一个字节,如,
struct packed_data
{ unsigned a:2;
unsigned b:3;
unsigned c:4;
int i;
}
struct packed_data;
如下图,
a b c i
2 3 4 16
对位段中的数据引用的方法,如,
data.a=2;
data.b=7;
data.c=9;
注意位段允许的最大值范围,如果写
data.a=8;
就错了,因为它只占 2位,最大值为 3.在此情况下,自动取赋
予它的数的低位,
例如,8的二进制数形式为 1000,而 data.a只有 2位,取 1000的
低 2位,故 data.a得值 0,
关于位段定义和引用的说明
1.若某一位段要从另一个字开始存放,可以用以下形式定义,
unsigned a:1;
unsigned b:2;
unsigned, 0;
unsigned c:3; (另一个单元 )
本来 a,b,c应连续存放在一个存储单元 (字 )中,由于用了长度为 0
的位段,其作用是使下一个位段从下一个存储单元开始存放,因
此,现在只将 a,b存储在一个存储单元中,c另放在下一个单元,
2.一个位段必须存储在同一存储单元中,不能距两个单元,如果
第一个单元空间不能容纳下一个位段,则该空间不用,而从下一
个单元起存放该位段,
一个存储单元
3.可以定义无名位段,如,
unsigned a:1;
unsigned,2; (这两位空间不用 )
unsigned b:3;
unsigned c:4
如下图所示,
a b c
4.位段的长度不能大于存储单元的长度,也不能定义位段数组,
5.位段可以用整型格式符输出,如,
printf(“%d,%d,%d”,data.a,data.b,data,c);
当然,也可以用 %u,%o,%x等格式符输出,
6.位段可以在数值表达式中引用,它会被系统自动地转换成整
型数,如,
data.a+5/data.b 是合法的,
7.不能引用段位的地址。