下面的左移和右移的探索都以int和unsigned int类型为例,先说明一下32位int和unsigned int各自的取值范围,其中int为:-2147483648 ~ 2147483647,unsigned int为:0~4294967295。
先说左移,左移就是把一个数的所有位都向左移动若干位,在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位。运行结果如下:
int k = 0x80000000; //16进制的80000000,为2进制的1000 0000...0000
printf("%d\n", k);
k = k >> 33;
printf("%x\n", k);
printf("%d\n", k);
运行结果:
就是说,符号位向右移动后,正数的话补0,负数补1,也就是汇编语言中的算术右移。同样当移动的位数超过类型的长度时,会取余数,然后移动余数个位。关于正数右移大家可以试试0x40000000。对于无符号整数,没了符号位的覆盖问题,一切都简单许多。这里就简要总结一下:
对于无符号整数的左移,如果超出范围(即最高位被挤掉),那么值是不正确的,也就是溢出,但绝对不会出现变成负数的情况。
对于无符号整数的右移,由于没有了符号位,所以高位是直接补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);
运行结果: