最近一直在自学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)
}