最近一直在自学go,然后看到很多用到位运算的,之前写PHP的,貌似比较少的接触,然后都忘记差不多了,借这个机会,就去找了下这些基础知识,然后这里记录一下,算是一个温故而知新吧。
在计算机中所有的数据都是以二进制形式存储的。位运算其实就是直接对在内存中的二进制数据进行操作的,因此处理数据的速度是非常快的。
基本的位操作符有与,或,异或,取反,左移,右移这6种,他们的运算规则如下所示:
位基础操作
符号 | 描述 | 运算规则 |
---|---|---|
& | 与 | 两个bit位都为1时,结果才为1 |
| | 或 | 两个位都为0时,结果才为0 |
^ | 异或 | 两个位相同为0,相异为1 |
~ | 取反 | 0变1,1变0 |
<< | 左移 | 各二进制位全部左移若干位,高位丢弃,低位补0 |
>> | 右移 | 各二进制位全部右移若干位,高位补0 |
下表列出了位运算&、|、^的计算
p | q | p&q | p|q | p^q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
对于十进制运算的过程呢,则是将十进制先行转换成二进制再进行位运算。
如:
十进制: 3&5 = 1
二进制: 0011&0101 = 0001
这个就是按位与将3的二进制上每个bit位与5的二进制的每个bit位比较,bit位上的数相同则保留,否则为0!
常用位操作小技巧
下面对位操作的一些常见应用作个总结,有判断奇偶,交换两数,变换符号,及求绝对值。
1、判断奇偶
只要根据最末尾是0还是1来决定,为0就是偶数,为1就是奇数。因此可以用if(a & 1) == 0 代替 if a%2 == 0来判断a是不是偶数
下面程序将输出0-100之间所有奇数
for i := 0; i < 100; i++ {
if (i & 1) == 1{
fmt.Printf("%d\n", i)
}
}
2、交换两数
一般的写法是:
func swap(a, b *int) {
if a != b {
c := *a
*a = *b
*b = c
}
}
当然了,go本身是直接支持 return a, b = b, a这种
这时就可以用位操作来实现交换两数而不用第三方变量
func swap(a, b *int) {
if a != b {
*a ^= *b
*b ^= *a
*a ^= *b
}
}
这段代码可以拆分来理解
第一步: a ^= b 即a = (a^b); 第二步: b ^= a 即b = b^(a^b), 由于^运算满足交换定律, b^(a^b) = b^b^a.由于一个数和自己异或的结果为0并且任何数与0异或都会不变的,所以此时b被赋上了a的值。 第三步: a ^= b 即a = a^b, 由前两步可知,a = (a^b), b = a, 所以 a = a^b 即 a = (a^b)^a。所以a会被赋上b的值。
再来看看个实例。
a := 10
b := 7
a的二进制位 1*8 + 0*4 + 1*2 + 0*1 = 1010
b的二进制位 1*4 + 1*2+ 1*1 = 111
第一步 a^=b = 1010 ^ 111 = 1101
第二步 b^=a = 111 ^ 1101 = 1010; b = 10
第三步 a^=b = 1010 ^ 1101 = 111; a = 7
3、求绝对值
位操作也可以用来求绝对值,对于负数可以通过对其取反后加1来得到正数。对-6可以这样:
1111 1010(二进制) –取反->0000 0101(二进制) -加1-> 0000 0110(二进制)
来得到6。
因此先移位来取符号位,int i = a » 31;要注意如果a为正数,i等于0,为负数,i等于-1。然后对i进行判断——如果i等于0,直接返回。否之,返回~a+1。完整代码如下:
func abs(a *int){
i := *a>>32
*a = i === 0 ? a : (^a + 1)
}
再分析下,对于任何数,与0异或都会保持不变,与-1即0xFFFFFF异或就相当于取反,因此,a与i异或后再减去i(因为i为0或者是-1,所以即是要么加0,要么加1)也可以得到绝对值。所以可以对上面代码优化下:
func abs(a *int){
i := *a>>32
*a = ((*a^i)-i)
}