Go 的位操作

翻译 Ellison ⋅ 于 4周前 ⋅ 214 阅读 ⋅ 原文地址

站点的翻译文章创建时,您将第一时间收到通知。

这是一篇协同翻译的文章,目前翻译进度为 73%,你可以点击『我来翻译』按钮来 参与翻译

file

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

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

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

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

wilson_yang 翻译于 3周前

& 运算符

在 Go 中, & 运算符在两个整型操作数中执行按位 AND 操作。AND 操作具有以下属性:

Given operands a, b
AND(a, b) = 1; only if a = b = 1
               else = 0

AND 运算符具有选择性的把整型数据的位清除为 0 的好的效果。 例如,我们可以使用 & 运算符去清除(设置)最后 4 个最低有效位(LSB)全部为 0 。

func main() {
    var x uint8 = 0xAC    // x = 10101100
    x = x & 0xF0          // x = 10100000
}
LiuKaHo 翻译于 3周前

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

func main() {
    var x uint8 = 0xAC    // x = 10101100
    x &= 0xF0             // x = 10100000
}

另外一个巧妙的技巧是:你可以用 & 操作去测试一个数字是奇数还是偶数。原因是当一个数字的二进制的最低位是 1 的时候,那他就是奇数。我们可以用一个数字和 1 进行 & 操作,然后在和 1 做 AND 运算,如果的到的结果是 1 ,那么这个原始的数字就是奇数

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 上运行上面的例子

miss201 翻译于 3周前

| 操作符

| 对其整型操作数执行按位操作。回想一下操作符具备以下性质:

Given operands a, b
OR(a, b) = 1; when a = 1 or b = 1
              else = 0 

我们可以利用按位操作符为给定的整数有选择地设置单个位。例如,在如下示例中我们使用按位将示例数(从低位到高位(MSB))中的第 3 ,第 7 和第 8 位置为 1 。

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

// 打印结果  11000100
            ^^   ^    

练习场中可运行范例。

wilson_yang 翻译于 3周前

在使用位掩码技术为给定的整型数字设置任意位时,运算非常有用。例如,我们可以扩展之前的程序为变量 a 存储的值设置更多的位。

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

// 打印结果 11000111

练习场中可以运行范例。

在前面的程序里,不仅要按位设置十进制的 196,而且要设置低位上的十进制 3。我们还可以继续(上更多的值)设置完所有的位。

wilson_yang 翻译于 3周前

位运算的配置用法

现在,回顾一下 AND(a, 1) = a 当且仅当 a = 1。 我们可以利用这个特性去查询其设置位的值。例如,在上述代码中 a & 196 会返回 196 是因为这几位的值在 a 中确实都存在。所以我们可以结合使用 ORAND 运算的方式来分别设置和读取某位的配置值。.

接下来的源码片段演示了这个操作。函数 procstr 会转换字符串的内容。它需要两个参数:第一个, str,是将要被转换的字符串,第二个, conf,是一个使用位掩码的方式指定多重转换配置的整数。

wilson_yang 翻译于 1周前
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 里的第二,三,四位的值为 14 来完成的。然后代码使用连续的 if 语句块来获取这些位操作进行对应的字符串转换。

miss201 翻译于 3周前

^ 操作符

在 Go 中 按位 异或 操作是用 ^ 来表示的。 异或运算符有如下的特点:

Given operands a, b
XOR(a, b) = 1; only if a != b
     else = 0

异或运算的这个特性可以用来把二进制位的一个值变成另外一个值。举个例子,给到一个 16 进制的值,我们可以使用以下代码切换前8位(从 MSB 开始)的值。

func main() {
    var a uint16 = 0xCEFF
    a ^= 0xFF00 // same a = a ^ 0xFF00
}

// a = 0xCEFF   (11001110 11111111)
// a ^=0xFF00   (00110001 11111111)
miss201 翻译于 3周前

在前面的代码片段中,与 1 进行异或的位被翻转(从 0 到 1 或从 1 到 0)。异或 运算的一个实际用途,例如,可以利用 异或运算去比较两个数字的符号是否一样。当 (a ^ b) ≥ 0 (或相反符号的 (a ^ b) < 0 )为 true 的时候,两个整数 a,b 具有相同的符号,如下面的程序所示:

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。在 Go Playground 上修改程序里 a ,b 的符号,以便看到不同的结果。

miss201 翻译于 2周前

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

不像其他语言 (c/c++,Java,Python,Javascript,等), Go 没有专门的一元取反位运算符。取而代之的是,XOR 运算符 ^,也可作为一元取反运算符作用于一个数字。对于给定位 x,在 Go 中 x = 1 ^ x 可以翻转该位。在以下的代码段中我们可以看到使用 ^a 获取变量 a 的取反值的操作。

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

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

练习场中可以运行范例。

wilson_yang 翻译于 8小时前

The &^ Operator

The &^ operator, reads as AND NOT, is a short-hand form that applies the AND and the NOT operations to its operands as shown in the following definition.

Given operands a, b
AND_NOT(a, b) = AND(a, NOT(b))

This has the interesting property of clearing the bits in the first operand if the second operand is 1 as defined here:

AND_NOT(a, 1) = 0; clears a
AND_NOT(a, 0) = a; 

The next code snippet uses the AND NOT operator to clear the last four LSBs in variable a from 1010 1011 to 1010 0000.

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

// prints:
10101011
10100000

Run on the Playground.

<< 和 >> 操作符

与其他 C 的衍生语言类似, Go 使用 <<>> 来表示左移运算符和右移运算符,如下所示:

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 运行代码

miss201 翻译于 3周前

Notice that with each shift, the LSB on the right is zero-filled. Inversely, using the right shift operator each bit in a value can shift to the right with the MSB zero-filled on the left as shown in the following example (signed numbers has an exception, see the Note on Arithmetic Shifts below).

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

// prints:
01111000
00111100
00011110

Run on the Playground.

Some of the simplest tricks that can be done with the left and right shift operators are the multiplication and division where each shift position represents a power of two. For instance, the following divides 200 (stored in a) by 2 with a right shift.

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

// prints:
100

Run on the Playground.

Or to multiply the value by 4, left shift by 2:

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

48

Run on the Playground.

The shift operators provides interesting ways to manipulate bits at designated position in a binary value. For instance, in the following snippet, the | and << operators are used to set the 3rd bit in variable a.

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

Run on the Playground.

Or you can combine the shift and the & operators to test if nth bit is set in a value as demonstrated in the following snippet.

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

// prints:
take action

Run on the Playground.

Using the &^ and the shift operators, we can unset the nth bit of a value. For instance, the following snippet unsets the third bit in variable a.

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

// prints:
1101
1001

Run on the Playground.

A Note on Arithmetic Shifts

When the value to be shifted (the left operand) is a signed value, Go automatically apply arithmetic shifts. During a right shift operation, the (two’s complement) sign bit is copied (or extended) to fill the shifted slots.

Conclusion

As with other modern languages, Go supports all bitwise operators. This writeup only provided an small sample of the sorts of bit hacks that can be done with these operators. You can find a lot more recipes online, specifically from Bit Twiddling Hacks by Sean Eron Anderson.

Follow Vladimir on Twitter @vladimirvivien!

If you are learning Go, checkout Vladimir Vivien's book on Go, titled Learning Go Programming from Packt Publishing.

This post was originally published on Medium by its author Vladimir Vivien as Bit Hacking with Go.

原文地址:https://dev.to/vladimirvivien/bit-hackin...

译文地址:https://golangcaff.com/topics/139/bit-op...


本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

回复数量: 0
    暂无评论~~
    您需要登陆以后才能留下评论!

    Gin 框架中文文档

    Iris 框架中文文档

    Go 简易教程

    Go Web 编程

    Go 编程基础(视频教程)

    Go 入门指南