Go中的位运算符

4,214 阅读4分钟

Go 位运算符的使用

位运算在平时写代码时不常用到,但是在很多的官方和非官方的优秀代码中经常会看到,被这些位运算的操作所惊艳。如果在平时的撸代码过程中,能够用到提升性能的位运算同时代码也显得很简洁,那何乐不为呢。

下面简单介绍下各个运算符以及可能会使用到的场景

二元运算符

  1. **&**与运算符 AND

    当两个数二进制位同为1的时候当前位置1,否则置0

    4 & 15 = 4
    ---------------
    00000100 &
    00001111
    00000100
    =4
    

    位运算的使用场景

    • 判断奇偶数

      常见的代码中可能是这样的

      func IsOdd(i int)bool{
        return i%2!=0
      }
      

      位运算判断

      func IsOdd(i int)bool{
        return (i & 1) == 1 //i是否为奇数取决于二进制的最后一位是1还是0 是1则为奇数 0则为偶数
      }
      
    • 计算数值的二进制位有多少个1

      很容易想到只要与1进行&运算,第一位如果为0结果为0 第一位如果结果为1 结果为1 利用这个特性数值右移一位 循环计算即可

      func BitCheck(i int)(count int){
        
        for i>0{
          count = count + (i&1)
          i >>= 1
        }
        return 
      }
      

      如果数值中间有多个0 需要多次判断,优化方法是值x如果不为0 那么和x-1进行与操作就会找出最低位的1

       /*
         10110001 &  //初始值 
         10110000    //减1后
        =10110000 &  //找出最低位的1
         10101111   //继续减1
        =10100000   //找出最低位的1
         ....       //直到找不出1即为0
        */
      
      func BitCheck(i int)(count int){
      	for i>0{
      			count += 1
      			i &= i-1
      	}
      	return
      }
      
  2. **|**或运算符 OR

    当两个数二进制位有一个为1时当前位置1,同为0的时候当前位才会置0

    4 | 15 = 15
    ---------------
    00000100 |
    00001111
    00001111
    =15
    
  3. **^**异或运算符 XOR

    **^**即可作为二元运算符,也可作为一元运算符。

    作为二元运算符,**^**是异或运算符。

    两个数的二进制位不同时,当前位才置1 否则置0

    有个很明显的规律 任何数和本身异或 结果为0, 0和任意数异或 结果为其本身

    4 | 15 = 11
    ---------------
    00000100 ^
    00001111
    00001011
    =11
    

    例子

    • 数值交换

      Go中数值交换可以直接通过a, b = b, a这样的方式来直接交换,位运算是怎么实现交换的呢?

      a ^= b // a = a^b
      b ^= a // b = b^(a^b) b和b自己异或为0 相当于 b=a
      a ^= b // a = (a^b)^a a在第一步中已经为a^b,现在的b 已经等于a a互相抵消 完成了值的交换
      
    • 寻找列表中只出现一次的数字

      假如一个列表中有2*N+1个数,其中一个数字只出现了一次。其他的数都出现了两次。如何寻找这个数字

      利用任何数和自己异或的结果将为0的特性

      func SingleNum(list []int)(target int){
        target = list[0]
        for _,val:=range list[1:]{
          target ^= val
        }
        return
      }
      
  4. **&^**位清空运算符 AND NOT

    看一个列子

    4 &^ 15 = 11
    ---------------
    00000100 &^
    00001111
    00000000
    =0
    

    位清空的意思是 **假如有 两个变量 var1 &^ var2 作位清空运算 **

    • 如果var2变量的位为0 则取var1变量对应的位值作为结果位
    • 如果var2变量的位为1 则结果位取0
  5. **<<>>**左移和右移运算符

    两个运算符左侧总是为要进行位移操作的变量,右侧为需要移动的位数值。

    之前因为看到32<<0=32就有点懵,以为是0左移32位 不明白为什么结果为32,就是因为把32当做要位移的位数了。

    1<<N = 2^N
    -----------
    1 左移多少位等于2的多少次方
    
    1024>>N = 1024/2^N
    ---------------
    右移N位 相当于除以2的N次方
    

    关于左移、右移 , 有一个Go在写磁盘单位GB MB KB等大小定义的例子

    在使用const关键字的时候 可使用内置变量 iota从0开始自动递增

    在遇到下个常量块或者单个常量定义的时候 也就是再一次使用const关键字的时候 iota置0

    package main
    type ByteSize float64
    const(
      B ByteSize = 1<<(10*iota)  // 1<<(10*0)
      KB  // 1<<(10*1)
      MB  // 1<<(10*2)
      GB  // 1<<(10*3)
      TB 	// 1<<(10*4)
      PB //  1<<(10*5)
    )