补码
在说位操作之前,还是要把计算机里面怎么存储来简单说下。补码有两条规则:
1、大于0:补码与原码相同。
2、小于0:符号位(最高位)为1,其他为绝对值的原码取反后加一。
比如来看-1的补码怎么得到:
1、绝对值的原码为:0x00000001
2、最高位设置1,得到:0x80000001
3、其他位取反得到:0xfffffffe
4、加一得到:0xffffffff
好了,为什么要这么纠结地搞出来一个补码?
正是因为有了补码形式,计算机的世界里面是没有减法的,其实容易看到如果把补码看成是一个正数,其值为:2^32-X,那么:
a、负数相加:(2^32 - X) + (2^32 - Y) = (2^32 - (X + Y)) + 2^32,那么如果X + Y没有溢出的话,这值显然表示的是-(X + Y)的补码。
b、正数加负数:X + (2^32 - Y) = 2^32 - (Y - X),如果X < Y,那么结果就是X - Y的补码,否则,X + (2^32 - Y) = 2^32 + (X - Y),结果就是X - Y的原码。
c、正数加正数:略。
那么从上面的这三种情况就知道为什么计算机中没有减法了。
与操作:
首先来介绍最简单的操作符:与,作用是在两个操作数的相同位都为1时,结果中对应位才为1。比如在检查int型x第三位是否为1时,可以这么写:
if ((x & 8) == 8) {
// TODO
}
这里前面为什么要加括号?原因很简单:位操作的优先级是很低的,如果不加括号会先运行8 == 8的比较~上面这种用处当然是&用到最多的地方,其实(x&8)还有另外的一层意思:把第三位之外的其他位都清零,还有另一个种情况,你想把第1位设置为0时可以x&0xfffffffe。那么&还有没有其他有意思的作用呢?比如下面这段代码:
System.out.printf("%x", (x & (-x)));
用一行代码就可以算出x中不为0的最低位是多少,这个方法还是挺有用的,在很多优化的时候都会用到。同样实现该功能的另一种写法是:System.out.printf("%x", (x & (1 - x)));
或操作:
或操作的含义是,只要操作数中有一个为1,那么结果中对应位则为1。同与操作,或操作也可以用来检查x的某一位是否为1,比如检查第三位,如下:
if ((x | 8) == x) {
// TODO
}
因为(x|8)的含义是,如果x第三位为1,那么保持不变,如果x第三位为0,那么变成1。或操作最大的用处就是将某位设置为1.
异或操作:
异或操作的含义是:两个操作数对应位置同为0或同为1时,结果中对应位为0,否则,结果中对应位为1。由其定义看到x^x = 0,那么用异或就可以很简单地解决下面这个问题了:
有N个数,其中只有一个数字出现的次数是奇数次,其他数字出现的次数都是偶数次,如何找到出现奇数次的数字?
把所有的数字全部异或的结果就是要找的数字了~
在接触异或时,感觉他比较神奇的地方是在博弈上的用法,一个最简单的例子:
有N堆石子,A和B轮流从其中的一堆石子中拿走任意个(当然不能超过这堆石子的总数),问:什么情况下,先手可以必胜?
大家从有两堆石子的情况下考虑一下应该能猜到:这N堆石子异或的结果不为0时先手必胜(博弈问题就此打住)~
非操作:
非操作的含义是:如果原来为1,那么结果中对应位为0,如果原来为0,那么结果中对应位为1。非操作是单操作数运算符,算是最简单的一个位操作了。
位移:
在Java中的位移有三种:
1、<<:左移,最右边用0补充。
2、>>:右移,最左边用符号位补充。
3、>>>:无符号右移,最左边用0补充。
首先,不要以为右移等价于除2(虽然大部分用到的时候是等价的)!比如:
System.out.println(1 >> 32);
这段代码你的预期结果是0,但实际上,输出是1,为什么会这样呢?在int移位的时候,1>>x实际上执行的是1>>(x%32),那么1>>32也就是相当于1>>0,没有移动。那么上面怎样就变成0了?如下:
System.out.println(1L >> 32);
输出为0,long对应执行的是1L>>(x%64)。大家最开始接触位移应该是遇到:
01字符串与int之间的相互转化~
这样类似的问题。位移中需要注意的地方的话就上面这个,其他的就没什么了~
END。