C/C++中的移位运算符——由二进制转换程序引发的思考

以前学习移位运算符的时候并没有太多关注它,而此次关于移位运算符的探究,主要源于写的一个二进制显示的程序:

#include <iostream>
using namespace std;

int main()
{
   int a=-1;
	
   for(int i=0;i<32;i++)
	   cout<<(( a & ( 1 << 31 ) >> i ) != 0);
   return 0;
}

写这个程序的目的就是显示a的二进制表示,思路如下:由于这里的编译器是32位的,因此循环输出32次,对于每次循环,都将a与(1<<31)>>i按位与,什么意思呢?因为我们要先输出a的最高位,因此每次循环的最开始就需要先将1左移31位得到最高位为1,其余位为0,再与a按位与,得到的结果判断是否为0,如果不为0,说明a最高位为1,否则为0;如此,进入第i次循环时,就判断a的高第i位的情况,这个时候就将1左移31位的结果右移i位即可。

这样来解释的话,看上去是没有问题的,但是实际测试的时候发现,如下:
在这里插入图片描述
在这里插入图片描述
可以看到,答案都是不正确的,这是为什么呢?
这是因为有符号数的右移通常是算术右移,而无符号数的右移通常是逻辑右移,算术右移k位是指将二进制位向右移k位,并在左端补k个最高位;逻辑右移即是在左端补k个0。比如说一个数是1000 0001,那么它算术右移1位结果就是1100 0000,逻辑右移的结果是0100 0000。
这一点明白了,那再来解释程序中的问题,在有符号与无符号一文中说过数字默认为有符号类型的,因此这里(1<<31)后得到的依旧是有符号数,此时再右移i位,实际上进行的是算术右移,在左端补最高位,以第一个程序中a=8为例,8的二进制表示为0000……1000第一次循环时a是和10000……0000按位与,结果等于0 ,输出0;第二次循环时,由于是算术右移,此时a就和110000……0000按位与,结果和第一次依然一样;这样一直循环到第29次循环,i=28,(1<<31)>>28结果就是1111 ……11000,此时a与其按位与,得到的结果肯定就非零了,因此输出1;再继续第30次循环,i=29,此时a与1111…… 11100按位与,可以发现,此时按位与的结果依然非零,输出1;不难推出,后面的第31次循环和第32次循环,答案都是输出1,这就是为什么出现上述程序的结果。

那么怎么修改程序呢?很简单,实际上我们需要的肯定是逻辑右移了,而逻辑右移的对象必须是无符号数,因此只需要将(1<<31)强制转换为无符号形式(这里1<<31肯定是正数,因此不会改变其值)即可,如下:
在这里插入图片描述
在这里插入图片描述
可以看到,此时的答案才是正确的。

那么为什么有符号数的右移通常是算术右移,而无符号数的右移通常是逻辑右移呢?

原因就在于 要保证数据的正确性。 我们知道,移位操作在直观上来看就是扩大或缩小2的倍数,因此以为操作是肯定不能改变原有数据的正负性的。这时再来看有符号数,因为有符号数的正负性说简单一点,就是由它的最高位来决定,

如果一个有符号数正数,那当然无论对它进行算术右移还是逻辑右移都是无所谓的,因为两种右移都是在高位补0,移位数据的正确性肯定是有保证的;但是如果一个有符号数是负数,它的最高位为1,那如果对它进行逻辑右移的话,情况就不一样了,因为逻辑右移是在最高位补0,那么一旦右移,最高位补0,这个数就成了正数,直观看就是一个负数除以2的倍数后成了正数,这完全就是不符合实际的;
再来看有符号数的算术右移。当然正数就不说了,来说一下负数。假设有一个有符号负数X,它的二进制位表示必然如下(假设有30个英文字母):
在这里插入图片描述
那么就有X=-2^31+a * 2 ^30+b * 2 ^29+ …… +x * 2 ^ 2+y * 2 ^ 1+z * 2 ^ 0;
算术右移1位后的X’的二进制表示如下:
在这里插入图片描述
那么就有X’=-2^31+ 2 ^ 30 + a * 2 ^29+b * 2 ^28+ …… +w * 2 ^ 2+x * 2 ^ 1+y * 2 ^ 0;

错位相减X-X’,得到X-X’=-2 ^ 30 +a +b +c + …… +x+y+z;

由于这里的a,b,c…x,y,z均是0或者1,因此X-X’=X’,得到X=2*X’,也就是说,算术右移1位,数值的确变为原来的一半,算术右移k位,变为原来的2^(-k),可见,算术右移保证了有符号数右移的正确性。

而对于无符号数,因为它始终是正数,因此不管是逻辑右移还是算术右移都不会改变它的正负,既然无关正负,那么就要保证数据的正确性了,显然,算术右移是无法保证数据右移的正确性的,因此无符号数必须采用逻辑右移。

补充说明

除了算术右移和逻辑右移以外,左移,实际上,左移并不分逻辑左移和算术左移,都是在最低位补充0,因为左移是会抛弃掉最高位的,这样左移后无论在最低位补什么,都是无法保证数据原来的正负,负数左移容易出现负数变正的情况,如下:
在这里插入图片描述
因此,为了保证左移的正确性,左移的对象在左移后应当保证其最高位不变,而符合这一要求的,就是所有非负数以及一部分负数,而这一部分负数并不能保证任意左移k位都能正确,唯一能保证任意左移k位都正确的负数只有-1(左移或右移k位实际上移动的位数是k与w取余的结果,w为编译器位数)。因此这一点必须要注意。

猜你喜欢

转载自blog.csdn.net/qq_28114615/article/details/85914123