2.4.5 位运算符和位表达式
位运算是指进行二进制位的运算。在C语言中,位运算符有位逻辑运算符和移位运算符。为了使没有学过汇编语言的读者对二进制位运算能有较好的理解,先介绍有关位的知识以及计算机中数值的编码表示方法。
1.字节和位
前已讲述,内存是以字节为单位的连续的存储空间,每个内存单元(字节(byte))有一个唯一的编号,即地址。
一个字节由8个二进制位(bit)组成,其中最右边的一位称为“最低位”,最左边的一位称为“最高位”。每一个二进制位的值是0或1。
2.数值的编码表示
在计算机内表示数值的时候,以最高位作为符号位,最高位为0表示数值为正,为1表示数值为负。表示数值,可采用不同的编码方法,一般有:原码、反码和补码。
(1)原码
用最高位作为符号位来表示数的符号:为0代表正数,为1代表负数,其余各位代表数值本身的绝对值(以二进制表示)。
例如:
+10的原码是:00001010
为简化起见,假如用一个字节(八个二进位)表示整数。如果用两个字节存放一个整数,情况是一样的,只是把+10表示成00000000 00001010而已。
+0的原码是:00000000
−0的原码是:10000000
显然,+0和−0表示的是同一个数0,而在内存中却有两个不同的表示。由于0的表示方法不唯一,不适合计算机的运算,所以在计算机内部一般不使用原码来表示数。
(2)反码
正数的反码与原码相同,如+10的反码也是00001010。
而负数的反码是:原码除符号位外(仍为1),各位取反。如−10的反码是11110101。
+0的反码是:00000000
−0的反码是:11111111
同样,0的表示不唯一。所以在计算机内部一般也不使用反码来表示数。
(3)补码
正数的补码与原码相同。如+10的补码同样是00001010。而负数的补码是:除最高位仍为1外,原码的其余各位求反,最后再加1。例如,−10的原码是:10001010,求反(除最高位外)后,得到11110101,再加1,结果是11110110。或者说,负数的补码是其反码加1。
+0的补码是:00000000
−0的补码是:11111111
所以,用补码形式表示数值0时,是唯一的,都是00000000。
现在计算机通常都是以补码形式存放数。因为采用补码形式不仅数值表示唯一,而且能将符号位与其他位统一处理。实际上采用补码,在计算机中可以使减法变为加法,为硬件实现提供方便。
3.位逻辑运算符与位逻辑表达式
位逻辑运算符有4种:
&(按位与);∣(按位或);~(按位取反);^(按位异或)。
由于它们都是按二进制位逐位地进行运算,相邻位之间不发生联系,即没有“进位”、“借位”等问题,所以称为位逻辑运算符。对参加逻辑运算的操作数,编译者是取其二进制的表达式进行的。
其中,除按位取反~是单目运算符外,其余3个是双目运算符。
位逻辑运算符的作用如表2.4.2所示。
表2.4.2 位逻辑运算符的作用
(1)“按位与”运算符(&)
按位与的运算规则是:如果两个相应的二进制位都为1,则该位的结果为1,否则为0。即:
1 &1= 1,1 &0= 0,0 &1= 0, 0 &0= 0
例如:对下面的两个数进行按位与运算。
unsigned int i=4988,j=63286;
i为:
0001001101111100(即0x137C或011574)
j为:
1111011100110110 (即0xF736或0173466)则i&j为:
&运算经常用于把特定位清零(屏蔽)。例如i的值为10100110,j的值为11100000,则i&j的结果是10100000,相当于把i的低5位屏蔽,高3位不变。可见,若要把某数的某些二进制位取出来,可以把其他位清零,把需要取出来的位同1做按位与即可。
注意:如果两个长度不同的数据(例如long型和int型)进行位运算时(如i&j,i为long型,j为int型),系统会将二者按右端对齐。如果j为正数,则左侧16位补满0;如果j为负数,则左侧16位补满1;如果j为无符号整型数,则左侧16位也补满0。
(2)“按位或”运算符(|)
按位或的运算规则是:两个相应的二进制位只要有一个为1,则该位的结果为1,否则为0。即:
1∣1= 1, 0∣1= 1, 1∣0= 1, 0∣0= 0
以上面的例子为例,i|j的结果为:
按位或|运算经常用于把一个数据的某些位定值为1。例如要想使一个数m的低4位改为1,只需将m与017进行按位或即可。
(3)“按位异或”运算符(^)
按位异或的运算规则是:如果参与运算的两个相应的二进制位相同,则该位的结果为0,否则为1。即:
1^1=0,0^0=0,1^0=1,0^1=1
仍以上面的例子为例,i^j的结果为:
按位异或^运算符能使特定位按位变反,方法是将这些特定位与1异或。例如i的值为10100110,j的值为11100000,则i^j的结果是01000110。凡是与1异或的位都变反了,而与0异或的位不变。
(4)“按位取反”运算符(~)
用来对一个二进制数按位取反,即将0变为1,1变为0。即:
~1=0,~0=1
注意:对于按位取反(~)来说,~0x7 (即~07或~7)在16位机上就是:
1111111111111000 (即0xFFF8或0177770)
而在32位机上却是:
11111111111111111111111111111000(即0xFFFFFFF8或037777777770)
所以,在C程序中,最好采用~0x7或~07来表示7的逻辑取反,而不要采用形如
0xFFF8、0177770或0xFFFFFFF8、037777777770
等表达式。主要原因是:前一种表达式与机器硬件特性无关,从而保证了程序的可移植性。
各个位逻辑运算符的优先级关系是:~最高,其余3个运算符的优先级从高到低依次是&、^、|,但三者都高于逻辑运算符而低于关系运算符。它们容易混淆,所以使用时注意加括号。例如:
n =((i&j)|k)
由位逻辑运算符和运算分量构成的表达式称做位逻辑表达式。位逻辑表达式中运算分量都应该是整型量或字符型量,不允许是浮点型量。
位逻辑运算符与逻辑运算符之间的区别:
①位逻辑运算符是针对二进制位的,而逻辑运算符是针对整个表达式的;位逻辑运算符要计算表达式的具体数值,而逻辑运算符只判断表达式的真与假。
②位逻辑运算符&、|和 ^的两个运算分量是可交换的;而逻辑运算符&&和||的两个运算分量是不可交换的,并且它们严格执行自左至右的运算。例如:
40&8 结果是8 40&&8 结果是1(真)
40|8 结果是40 40||8 结果是1(真)
0||x 结果是1(若x≠0) 或0(若x= 0)
0&&x 结果是0,其中x是任意表达式
位逻辑运算符通常用于与硬件相应的程序中,如设备驱动程序,对表示状态字中的某些位进行测试、设置和屏蔽等。
4.移位运算符和移位表达式
C语言中实现移位功能的运算符有两个:“〈〈”(左移)和“〉〉”(右移)。
它们都是双目运算符,并且要求两个运算分量都是整型量。由移位运算符和运算分量构成的表达式称为移位表达式。
(1)左移位运算符〈〈
它的一般使用形式是:
表达式1〈〈表达式2
其中,“表达式1”是移位对象,“表达式2”是表示移位的位数。它的功能是:把表达式1的值(以二进制形式表示)向左移动n位,n值由表达式2确定(该值必须是正整数)。
例如,m=00001011,那么,移位表达式
m=m〈〈3
的结果是01011000,即把m的各二进制位全部向左移3位,右边空出的位补0,而左边溢出的位被丢弃不管。
在容许的范围内,利用左移位运算可扩大原数的倍数,左移1位扩大2倍,左移2位扩大4倍,即可实现被移对象乘以n2功能。例如,上面m的值是11,左移3位后,结果值是88,恰好等于11×2³ =11×8=88。
(2)右移运算符〉〉
它的一般使用形式是:
表达式1〉〉表达式2
其中,“表达式1”是移位对象,“表达式2”是表示移位的位数。它的功能是:把表达式1的值(以二进制形式表示)向右移n位,n的值由表达式2确定(该值必须是正整数)。
例如,m=00001011,那么,移位表达式
m= m〉〉2
的结果是00000010,即:把m的各二进制位全都向右移2位,右边溢出的位被丢弃,而左边空出的位(在本例情况下)补0。
右移一位相当于该数除以2,右移n位相当于该数除以n2。
在右移时,要注意符号位问题。如果移位对象是无符号数,那么右移时左边空出来的位全以0填充,这种方式称为逻辑右移方式;如果移位对象是有符号数,当移位对象是正数(符号位为0)时,左边空位用0填充;当移位对象是负数(符号位为1)时,左边空位是补0还是补1,要取决于所用的计算机系统。有的系统按逻辑右移方式(即补0)处理,有的系统则按算术右移方式(即补1)处理。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。