在计算机内存昂贵,处理能力有限的美好旧时光里,用比较黑客范的位运算方式去处理信息是首选方式(某些情况下只能如此)。时至今日,直接使用位运算仍然是很多计算领域中不可或缺的部分,例如底层系统编程,图形处理,密码学等。

Go 编程语言支持以下按位运算符:

&   bitwise AND
 |   bitwise OR
 ^   bitwise XOR
&^   AND NOT
<<   left shift
>>   right shift

本文的余下部分详述了每个操作符以及它们如何使用的案例。

& 运算符

&ANDAND
Given operands a, b
AND(a, b) = 1; only if a = b = 1
               else = 0
AND&
func main() {
    var x uint8 = 0xAC    // x = 10101100
    x = x & 0xF0          // x = 10100000
}

所有的位运算都支持简写的赋值形式。 例如,前面的例子可以重写为如下。

func main() {
    var x uint8 = 0xAC    // x = 10101100
    x &= 0xF0             // x = 10100000
}
&&AND
import (
    "fmt"
    "math/rand"
)
func main() {
    for x := 0; x < 100; x++ {
        num := rand.Int()
        if num&1 == 1 {
            fmt.Printf("%d is odd\n", num)
        } else {
            fmt.Printf("%d is even\n", num)
        }
    }
}

在 Playground 上运行上面的例子

| 操作符

|或或
Given operands a, b
OR(a, b) = 1; when a = 1 or b = 1
              else = 0
或或
func main() {
    var a uint8 = 0
    a |= 196
    fmt.Printf("%b", a)
}

// 打印结果  11000100
            ^^   ^

练习场中可运行范例。

或a
func main() {
    var a uint8 = 0
    a |= 196
    a |= 3
    fmt.Printf("%b", a)
}

// 打印结果 11000111

在练习场中可以运行范例。

位运算的配置用法

AND(a, 1) = a 当且仅当 a = 1a & 196aORAND
procstrstrconf
const (
    UPPER  = 1 // 大写字符串
    LOWER  = 2 // 小写字符串
    CAP    = 4 // 字符串单词首字母大写
    REV    = 8 // 反转字符串
)

func main() {
    fmt.Println(procstr("HELLO PEOPLE!", LOWER|REV|CAP))
}

func procstr(str string, conf byte) string {
    // 反转字符串
    rev := func(s string) string {
        runes := []rune(s)
        n := len(runes)
        for i := 0; i < n/2; i++ {
            runes[i], runes[n-1-i] = runes[n-1-i], runes[i]
        }
        return string(runes)
    }

    // 查询配置中的位操作
    if (conf & UPPER) != 0 {
        str = strings.ToUpper(str)
    }
    if (conf & LOWER) != 0 {
        str = strings.ToLower(str)
    }
    if (conf & CAP) != 0 {
        str = strings.Title(str)
    }
    if (conf & REV) != 0 {
        str = rev(str)
    }
    return str
}

在 Playground上面运行代码.

procstr("HELLO PEOPLE!", LOWER|REV|CAP)conf

^ 操作符

异或^异或
Given operands a, b
XOR(a, b) = 1; only if a != b
     else = 0
异或
func main() {
    var a uint16 = 0xCEFF
    a ^= 0xFF00 // same a = a ^ 0xFF00
}

// a = 0xCEFF   (11001110 11111111)
// a ^=0xFF00   (00110001 11111111)
异或异或(a ^ b) ≥ 0(a ^ b) < 0true
func main() {
    a, b := -12, 25
    fmt.Println("a and b have same sign?", (a ^ b) >= 0)
}

在 Go 的 Playground运行代码。

a and b have same sign? false

^ 作为取反位运算符 (非)

XOR^^aa
func main() {
    var a byte = 0x0F
    fmt.Printf("%08b\n", a)
    fmt.Printf("%08b\n", ^a)
}

// 打印结果
00001111     // var a
11110000     // ^a

在练习场中可以运行范例。

&^ 操作符

&^与非与非
Given operands a, b
AND_NOT(a, b) = AND(a, NOT(b))

如果第二个操作数为 1 那么它则具有清除第一个操作数中的位的趣味特性。

AND_NOT(a, 1) = 0; clears a
AND_NOT(a, 0) = a;
AND NOT1010 10111010 0000
func main() {
    var a byte = 0xAB
     fmt.Printf("%08b\n", a)
     a &^= 0x0F
     fmt.Printf("%08b\n", a)
}

// 打印:
10101011
10100000

在练习场中运行范例。

<< 和 >> 操作符

<<>>
Given integer operands a and n,
a << n; shifts all bits in a to the left n times
a >> n; shifts all bits in a to the right n times
a00000011
func main() {
    var a int8 = 3
    fmt.Printf("%08b\n", a)
    fmt.Printf("%08b\n", a<<1)
    fmt.Printf("%08b\n", a<<2)
    fmt.Printf("%08b\n", a<<3)
}

// 输出的结果:
00000011
00000110
00001100
00011000

在 Playground 运行代码

注意每次移动都会将低位右侧补零。相对应,使用右移位操作符进行运算时,每个位均向右方移动,空出的高位补零,如下示例 (有符号数除外,参考下面的算术移位注释)。

func main() {
 var a uint8 = 120
 fmt.Printf("%08b\n", a)
 fmt.Printf("%08b\n", a>>1)
 fmt.Printf("%08b\n", a>>2)
}

// 打印:
01111000
00111100
00011110

在 练习场中可以运行范例。

200
func main() {
    a := 200
    fmt.Printf("%d\n", a>>1)
}

// 打印:
100

在 练习场 中可以运行范例。

或是通过左移 2 位,将一个数乘以4:

func main() {
    a := 12
    fmt.Printf("%d\n", a<<2)
}
// 打印:

48

在 练习场 中可以运行范例。

|<
func main() {
    var a int8 = 8
    fmt.Printf("%08b\n", a)
    a = a | (1<<2)
    fmt.Printf("%08b\n", a)
}
// prints:
00001000
00001100

可以在 练习场 中运行代码示例。

&
func main() {
    var a int8 = 12
    if a&(1<<2) != 0 {
        fmt.Println("take action")
    }
}

// 打印:
take action

在 练习场中运行代码。

&^
func main() {
    var a int8 = 13 
    fmt.Printf("%04b\n", a)
    a = a &^ (1 << 2)
    fmt.Printf("%04b\n", a)
}

// 打印:
1101
1001

在 练习场 中运行代码。

关于算术位移运算的笔记

当要位移的值(左操作数)是有符号值时,Go 自动应用算术位移。在右移操作期间,复制(或扩展)二进制补码符号位以填充位移的空隙。

总结

与其它现代运算符一样,Go 支持所有二进制位操作运算符。这篇文章仅仅提供了可以用这些操作符完成的各种黑科技示例。你可以在网络上找到很多文章,特别是 Sean Eron Anderson 写的 Bit Twiddling Hacks 。

关注 Vladim @vladimirvivien 的 Twitter。

如果你正在学习 Go,阅读 Vladimir Vivien 关于 Go 的书,名为 Learning Go Programming 。

这篇文章开始由作者 Vladimir Vivien 发布在 Medium 上,名为 Bit Hacking with Go。

译文地址:https://learnku.com/go/t/23460/bit-operation-of-go