常于算法中见闻各路奇淫技巧,以位运算解之甚速,怎奈小生对该法理解过于浅薄,不得已,遂学之。
一、按位与
将两数转为二进制,逐位相与即可。
int a = 6 & 11; //输出a=2
6 => 110 11=> 1011
6 & 11 => 110 & 1011 = 0010 => 2
二、按位或
和按位与
类似,将两数转为二进制,逐位相或即可。
int b = 6 | 11; //输出b=15
6 => 110 11=> 1011
6 & 11 => 110 | 1011 = 1111=> 15
三、按位异或
异或的意思简单来说就是:将两数转为二进制,每一位分别进行不进位
的加法运算即可。
int c1 = 6 ^ 11; //输出c1=13
int c2 = 6 ^ 6; //输出c2=0
6 => 110 11=> 1011
c1: 6 ^ 11 => 110 ^ 1011 = 1101=> 13
c2: 6 ^ 6 => 0110 ^ 0110 = 0000 => 0
四、按位取反
取反的步骤比较复杂,要先将数字转化为首位为符号位的二进制数字,再对每一位(包括符号)进行取反,此时得到的是最终结果的补码
,再将补码转换为原码,才能得到结果。
int d1 = ~6; //输出d1=-7
int d2 = ~-6; //输出d2=5
6 => 110
d1取反步骤如下:
- 6=>0110 (第一位0是符号位,表示正数)
- 0110按位取补得到1001 (第一位1是负号)
- 对1001取补,因为负数取补需要先对非符号位-1,所以1001的补数为1000的位反,得到1111
- 1111=>-7
d2取反步骤如下:
- -6=>1110
- 1110取补先-1,所以1110=>1101=>0010
- 0010按位反得到0101
- 0101=>5
五、左移
将数字转换为二进制,以末尾补0的方式将每一位向左移动对应位数即可。
int e = 6 << 2; //输出e=24
6 =>0000 0110
向左移动2位得到
0001 1000 => 24
根据例子与二进制的规则可以总结出,a << b实际上就是a*2b。
通常认为a<<1
比a*2
要快,因为前者是更底层一些的操作。因此程序中乘以2的操作请尽量用左移一位来代替。
六、右移
1. 正数的右移
与左移类似,正数的右移转换为二进制后向右移动即可。
int f1 = 11 >> 2; //输出f1=2
11 =>0000 1011
向右移动2位得到
0000 0010 => 2
2. 负数的右移
因为负数在内存中是以补码
形式存在的,所以首先根据负数的原码求出负数的补码,再以高位补1的方式进行移动,移动后再求出原码即为最终结果。
int f2 = -11 >> 2; //输出f2=-3
-11 => 1000 1011
取补码:1111 0100
向右移动:1111 0100 => 1111 1101
非符号位减一:1111 1101=>1111 1100
取反得到原码:1000 0011 => -3
与向左类似,a>>b即为a/2b再取整,所以相应地,我们也应当使用a>>1来代替a/2这种写法。