C语言的移位操作是程序员日常使用非常频繁的操作之一,用好了移位操作可以为我们省去不少的时间并且程序也可以写得更加漂亮。我们说移位操作是可以分为
左移 和
右移 的,左移的位操作符是
<<,左移一位表示将原数值乘以
2(
如果左移使其溢出了将不能得到正确的结果);右移的位操作符是
>>,右移一位表示将原数值除以
2。例如左移操作,如下图所示:
左移3位本质上就是将左边 3 位去掉,在右边用 3 个 0 补全,这就是左移了。看似移位操作很简单,其实这里面是有坑存在的。
如果你认为右移也是这样的话,那么你就掉坑里了,坑就坑在右移上。因为右移是又可以分为逻辑右移(slr) 和 算术右移(sar) 两种方式的。因为右移操作存在一个左移不增面临的问题:从左边移入新位的时候,可以有两种方案。一种是逻辑移位,左边移入的位用 0 填充;另一种是算术移位,左边移入的位由该值得符号位决定,符号位为 1 则移入 1 ,符号位为 0 则移入 0 ,这样移位可以保证原数值的正负性质不变。如果值 10010110 右移两位,逻辑移位的结果是 00100101,算术移位的结果则是 11100101。
左移没有算术左移和逻辑左移之分(因为算术左移和逻辑左移是相同的)
标准 说明无符号值执行的所有移位操作都是逻辑移位,但对于有符号值,到底是采用逻辑移位还是算术移位取决于编译器。你可以编写一个简单的测试程序,看看你的编译器使用哪种移位方式,但这并不能保证其他编译器也会用同样的方式。因此,一个程序如果使用了有符号数的右移操作,他就是不可移植的。
我自己在PC上做了测试,代码如下
代码如下 |
复制代码 |
#include
int main(int argc, char const *argv[])
{
int a = -4; //计算机存储是以补码的形式存储的,所以是 11111100
printf("before a=%dn", a);
a = a >> 1; // 右移一位
printf("after a=%dn", a); // 如果是-2则是算术右移,如果是126则是逻辑右移
return 0;
}
|
执行结果如下图所示:
说明在Windows上VS2015环境下编译器是采用算术右移的。
我们可能会遇到类似这样的情况:
a << -5
左移 -5 位表示什么呢?是表示右移 5 位么,还是根本不移位?又或者移位的位数比操作数的位数还要多,会发生什么呢?
标准 说明这类位移的行为是未定义的,所以他是由编译器决定的。所以我们应该尽量避免这种情况的发生,它会导致我们的程序出现一些你无法预料的错误,并且使用这类移位操作是不可移植的。