前言
计算机中的所有数据都是按照bit为单位进行存储的,由于二进制存储的特殊性,编程实现中出现了很多位操作技巧,这里做一下总结。总结的内容借鉴了网友博客、书籍内容,然后还有个人理解,侵删。
1-存储数字
先来看一道牛客上的题目:
int main(){
int i=-2147483648;//这里有问题,后文说
printf("%d,%d,%d,%d",~i,-i,1-i,-1-i);
return;
}
结果是:2147483647,-2147483648,-2147483647,2147483647
首先,计算机中存储数字都是按照其补码来存储的,好处是将加减法统一为补码的加法,也就是将+、-符号转换为数字本身的一部分。整数的原码、反码、补码相等,就是原码;负数的补码是通过原码求反码,反码+1得到的结果。同时规定:
0
的补码0x00000000
,-2147483648
的补码:0x80000000
~i
i
的补码为1000 0000 0000 0000 0000 0000 0000 0000
,取反0111 1111 1111 1111 1111 1111 1111 1111
,此为补码,符号位为0,表示正数,正数原码补码一致,因而该数即表示2^31-1,即2147483647 。注意,这里的取反(~)是数值位和符号位同时取反,在负数求反码取反过程中只是数值位取反。
-i
要对一个数值执行单目运算符-
,表示的是对该数取反然后再+1,也即是我们常说的求补运算,注意这里取反+1与原码求补码的区别!求补运算与求补码是不一样的!例子(4位有符号整数):x=-4 1100(补码) -x=~x+1 也即是 0011+0001=0100(4),而1100再求补码应是先数值位取反,即1011,然后+1,变成1100!
1-i
我们已经求出-i
的补码为1000 0000 0000 0000 0000 0000 0000 0000
加上1的补码即为1000 0000 0000 0000 0000 0000 0000 0001
。该补码表示的原码为1 111 1111 1111 1111 1111 1111 1111 1111
,即为-2147483647
。如果这里想通过补码直接验证对应数字的具体值,那么可以把补码转换成对应的十六进制形式,直接打印即可。cout << 0x80000001;
打印结果就是-2147483647
。
-1-i
-1
的补码为1 111 1111 1111 1111 1111 1111 1111 1111
,加上-i补码 1000 0000 0000 0000 0000 0000 0000 0000
,得0111 1111 1111 1111 1111 1111 1111 1111
,即 2147483647
。
2-0x8000000
上文中提到代码中int i = -2147483648;
这段代码有一定问题。问题和编译器有关。在cb中这是没问题的,i
就是INT_MIN
;但是在VS2013中,编译器先判断2147483648
并不在int
类型范围内,自动转换为unsigned int
类型,然后给unsigned int
类型赋予负号,这里就错了。如果想在VS中获得INT_MIN
,应该写如下代码:
int i = 0x80000000;
还有一个典型问题,看如下代码。
if (1 < 0x80000000)//数字1,不是字母l
cout << "ok1";
int i = 0x80000000;
if (1 < i)
cout << "ok2";
运行结果只输出ok1
。为什么呢?因为如果你在代码中直接写0x80000000
,那么编译器(cb和vs都是这样)是直接判断2147483648
这个数字不在int
范围内,提升为unsigned int
类型,它的值就是2147483648
,而不是预想的-2147483648
。但是如果你对一个int变量赋予值0x80000000
,折相当于告诉了编译器这是一个signed int
类型,需要按照有符号数的方式去存储它,所以这时i的值就是-2147483648
。
如果你写了如下代码
int i = 0x80000000;
cout << (unsigned int)i;
那么,输出将是2147483648
,编译器按照无符号数字来处理。
3-数字元整问题
在SGI-STL
的二级配置器源码中,有这么一个功能函数,是把用户申请的以bytes为单位的空间大小(size_t n)向上转换成8的倍数。如7->8,9->16;
这里的代码是:
//_ALIGN = 8;
static size_t ROUND_UP(size_t n) {
return ((n) + _ALIGN-1) & ~(_ALIGN-1);
}
根据二进制数字的性质,那么一个数字的二进制形式的最后三位是0,那么这个数字肯定就是8的整数倍;但是需要额外考虑的情况是,n二进制形式的最后三位原本不是0,比如9(1001)
,所以使用(n)+_ALIGN-1
操作,进位后变为0,然后在使用& ~(_ALIGN-1)
操作使得二进制数字最后三位是0。这里的位操作使用非常灵活,而且效率极高。
4-基本的位操作
Set a bit
number |= 1 << x;
Clear a bit
number &= ~(1 << x);
Toggle a bit
number ^= 1 << x;
Check a bit(is 1 or 0)
bit = (number >> x) & 1;
5-循环移位
int
类型的变量,因为超过32位,会出现什么情况呢?
会将移动的位数对32取模运算,根据余数进行移位,这就是循环移位。
但考虑一下下面的代码(在codeblocksGNU GCC Compiler)
int a = 0x00000001;
int b = 0x00000001;//a和b都是1
b<<=32;//直接移位32位,发生循环移位,32%32==0,相当于不移位
cout << b << endl;
cout << a <<endl;
a<<=31;//这时候等于INT_MIN(-2147483648)
cout << a << endl;
a<<=1;//分步移位,这时候在向左移一位就是0
cout << a;//0
如果还想了解更多的位操作内容,可以浏览StackOverflow
BIt Operation总结