下面的左移和右移的探索都以int和unsigned int类型为例,先说明一下32位int和unsigned int各自的取值范围,其中int为:-2147483648 ~ 2147483647,unsigned int为:0~4294967295。
int类型
先说左移,左移就是把一个数的所有位都向左移动若干位,在C中使用的是<<运算符,等于将该数乘以若干个2。下面举几个个例子来说明左移一些要注意的情况:
例子1:左移导致符号位被覆盖
int k = 0x40000000; //16进制的40000000,为2进制的0100 0000...0000
printf("%d\n", k);
k = k << 1;
printf("%x\n", k);
printf("%d\n", k);
结果为:
k是正数,左移一位后为0x80000000(补码),对应的十进制为-214783648,很显然溢出了(实际上应该是1073741824*2=2147483648>2147483647,这超过了int类型的表示范围的最大值)。究其原因是左移时导致符号位0被1覆盖,成了1000 0000....0000(补码),十进制为-2147483648,是int所能表示的最小值。由此我们可以再进一步猜测,如果K再左移一位,那么1000 0000....0000变成了0000 0000....0000,那么就是0。(大家可以自己试试看 ^_^)。从上面来看,int类型左移时,符号位也要参与其中。
另外如果左移到符号位的那位和符号位一致,那么左移就是成功的,本质上也就是没有超过其最大取值范围。例如:
int k = 0x20000000; //16进制的20000000,为2进制的0010 0000...0000
printf("%d\n", k);
k = k << 1;
printf("%x\n", k);
printf("%d\n", k);
运行结果为:
可以看到左移结果是正确的。
再来看看负数情况如何:
int k = 0xA0000000; //16进制的A0000000,为2进制的1010 0000...0000
printf("%d\n", k);
k = k << 1;
printf("%x\n", k);
printf("%d\n", k);
运行结果为:
k是负数,左移一位后为0x40000000,对应的十进制为1073741824,很显然溢出了(实际上应该是-1610612736*2=-3221225472<-2147483648,这超过了int类型的最小表示范围)。究其原因是左移时导致符号位1被0覆盖,成了0100 0000....0000(补码),十进制为1073741824。因此,对于int类型左移时是要好好考虑是否超出了值的范围。
例子2、左移里的位数超过该数值类型的最大位数
int k = 0x00000001;
printf("%d\n", k);
k = k << 33;
printf("%x\n", k);
printf("%d\n", k);
在VS2013中,编译器会给出一个警告:
对于32位int,那么实际上k移动的是33%32后的余数,也就是1位。运行结果如下:
右移的概念和左移相反,就是往右边挪动若干位,运算符是>>,相当于该数除以若干个2。
右移对符号位的处理和左移不同,对于有符号整数来说,右移会保持符号位不变,然后在左边填充0,例如:
int k = 0x80000000; //16进制的80000000,为2进制的1000 0000...0000
printf("%d\n", k);
k = k >> 33;
printf("%x\n", k);
printf("%d\n", k);
运行结果:
unsigned int类型
对于无符号整数,没了符号位的覆盖问题,一切都简单许多。这里就简要总结一下:
对于无符号整数的左移,如果超出范围(即最高位被挤掉),那么值是不正确的,也就是溢出,但绝对不会出现变成负数的情况。
对于无符号整数的右移,由于没有了符号位,所以高位是直接补0。下面看两个例子即可:
unsigned int k = 0xfffffffe;
printf("%u\n", k);
k = k << 1;
printf("%x\n", k);
printf("%u\n", k);
运行结果:
unsigned int k = 0x0000010;
printf("%u\n", k);
k = k >> 1;
printf("%x\n", k);
printf("%u\n", k);
运行结果: