2.4.5 位运算符和位表达式
位运算是指进行二进制位的运算。在C语言中,位运算符有位逻辑运算符和移位运算符。如果对汇编语言有了解的读者,可以跳过本小节。
为简单起见,假设内存的一个存储单元占用一个字节,一个整数占用2个字节。读者可以自行推广到其他情况(如内存的一个存储单元占用一个字或双字等)。
1.字节和位
内存是以字节为单位的连续的存储空间,每个内存单元有一个唯一的编号,即地址。
一个字节由8个二进制位(bit)组成,其中最右边的一位称为“最低位”,最左边的一位称为“最高位”。每一个二进制位的值是0或1。
2.数值的编码表示
在计算机内表示数值的时候,以最高位作为符号位,最高位为0表示数值为正,为1表示数值为负。表示数值,可采用不同的编码方法,一般有:原码、反码和补码。
1)原码
用最高位作为符号位来表示数的符号:为0代表正数,为1代表负数,其余各位代表数值本身的绝对值(以二进制表示)。
例如:
+10的原码是:0000000000001010
+0的原码是:0000000000000000
−0的原码是:1000000000000000
+0和−0表示的是同一个数0,而在内存中却有两个不同的表示。由于0的表示方法不唯一,不适合计算机的运算,所以在计算机内部一般不使用原码来表示数。
2)反码
正数的反码与原码相同,如+10的反码也是0000000000001010。
负数的反码是:原码符号位外仍为1,其他各位取反。如−10的反码是1111111111110101。
+0的反码是:0000000000000000
−0的反码是:1111111111111111
同样,0的表示不唯一。所以在计算机内部一般也不使用反码来表示数。
3)补码
正数的补码与原码相同。如+10的补码同样是0000000000001010。而负数的补码是:除最高位仍为1外,原码的其余各位求反,最后再加1。例如,−10的原码是:1000000000001010,求反(除最高位外)后,得到1111111111110101,再加1,结果是1111111111110110。或者说,负数的补码是其反码加1。
+0的补码是:0000000000000000
−0的补码是:1111111111111111
用补码形式表示数值0时,是唯一的,都是0000000000000000。
计算机通常都是以补码形式存放数。因为采用补码形式不仅数值表示唯一,而且能将符号位与其他位统一处理。实际上采用补码,在计算机中也可以使减法变为加法,为硬件实现减法提供方便。
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的运算为
0001001101111100
&运算经常用于将特定位清零(屏蔽)。例如i的值为1000000000100110,j的值为1111111111100000,则i&j的结果是1000000000100000,相当于将i的低5位屏蔽,高11位不变。可见,若要将某数的某些二进制位取出来,可以将其他位清零,将需要取出来的位同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的结果为
0001001101111100
按位或|运算经常用于将一个数据的某些位定值为1。例如要想使一个数m的低4位改为1,只需将m与017进行按位或即可。
3)按位异或运算符^
按位异或的运算规则是:如果参与运算的两个相应的二进制位相同,则该位的结果为0,否则为1。即
1^1 = 0,0^0 = 0,1^0 = 1,0^1 = 1
仍以上面的例子为例,i^j的结果为
0001001101111100
按位异或^运算符能使特定位按位变反,方法是将这些特定位与1异或。例如i的值为1000000000100110,j的值为1111111111100000,则i^j的结果是0111111111000110。凡是与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)左移位运算符<<
它的一般使用形式是:
整型变量<<表达式
其中,表达式表示移位的位数。它的功能是:将变量的值(以二进制形式表示)向左移动n位,n值由表达式确定(该值必须是正整数)。整个表达式的值是变量移位后的值,而变量本身值不变。
例如,m=0000000000001011,那么移位表达式
m<<3的结果是0000000001011000,即将m的各二进制位全部向左移3位,右边空出的位补0,而左边溢出的位被丢弃不管。
在容许的范围内,对于正数,利用左移位运算可扩大原数的倍数,左移1位扩大2倍,左移2位扩大4倍,即可实现被移对象乘以n2功能。例如, m的值是11,左移3位后,结果值是88,等于11×2³ =11×8=88。
2)右移运算符>>
它的一般使用形式是:
变量>>表达式
其中,表达式表示表示移位的位数。它的功能是:将变量的值(以二进制形式表示)向右移n位,n的值由表达式确定(该值必须是正整数)。整个表达式的值是变量移位后的值,而变量本身值不变。
例如,m=0000000000001000,那么,移位表达式
m>>2
的结果是0000000000000010,即:将m的各二进制位全都向右移2位,右边溢出的位被丢弃,而左边空出的位(在本例情况下)补0。
对于正数,右移一位相当于该数除以2,右移n位相当于该数除以n2。
在右移时,要注意符号位问题。如果移位对象是无符号数,那么右移时左边空出来的位全以0填充,这种方式称为逻辑右移方式;如果移位对象是有符号数,当移位对象是正数(符号位为0)时,左边空位用0填充;当移位对象是负数(符号位为1)时,左边空位是补0还是补1,要取决于编译系统。有的系统按逻辑右移方式(即补0)处理,有的系统则按算术右移方式(即补1)处理。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。