一文詳解Golang中的位元運算

2022-09-21 14:00:30
本篇文章帶大家深入瞭解下Golang中的位元運算,介紹一下詳述每個操作符以及它們如何使用的案例,希望對大家有所幫助!

php零基礎到就業直播視訊課:進入學習
程式設計師必備介面測試偵錯工具:

在計算機記憶體昂貴,處理能力有限的美好舊時光裡,用比較駭客範的位運算方式去處理資訊是首選方式(某些情況下只能如此)。時至今日,直接使用位運算仍然是很多計算領域中不可或缺的部分,例如底層系統程式設計,圖形處理,密碼學等。【相關推薦:Go視訊教學

Go 程式語言支援以下按位元運運算元:

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

本文的餘下部分詳述了每個操作符以及它們如何使用的案例。

& 運運算元

在 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
}

所有的位運算都支援簡寫的賦值形式。 例如,前面的例子可以重寫為如下。

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

| 操作符

| 對其整型運算元執行按位元操作。回想一下操作符具備以下性質:

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
            ^^   ^

練習場中可執行範例。

在使用位掩碼技術為給定的整型數位設定任意位時,運算非常有用。例如,我們可以擴充套件之前的程式為變數 a 儲存的值設定更多的位。

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

// 列印結果 11000111

練習場中可以執行範例。

在前面的程式裡,不僅要按位元設定十進位制的 196,而且要設定低位上的十進位制 3。我們還可以繼續(上更多的值)設定完所有的位。

位運算的設定用法

現在,回顧一下 AND(a, 1) = a 當且僅當 a = 1。 我們可以利用這個特性去查詢其設定位的值。例如,在上述程式碼中 a & 196 會返回 196 是因為這幾位的值在 a 中確實都存在。所以我們可以結合使用 ORAND 運算的方式來分別設定和讀取某位的設定值。.

接下來的原始碼片段演示了這個操作。函數 procstr 會轉換字串的內容。它需要兩個引數:第一個, str,是將要被轉換的字串,第二個, conf,是一個使用位掩碼的方式指定多重轉換設定的整數。

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 語句塊來獲取這些位元運算進行對應的字串轉換。

^ 操作符

在 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)

在前面的程式碼片段中,與 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 的符號,以便看到不同的結果。

^ 作為取反位運運算元 (非)

不像其他語言 (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

練習場中可以執行範例。

&^ 操作符

&^ 操作符意為 與非,是 操作符的簡寫形式,它們定義如下。

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 NOT 操作符,將變數值1010 1011變為 1010 0000,清除了運算元上的低四位。

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

// 列印:
10101011
10100000

練習場中執行範例。

<< 和 >> 操作符

與其他 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 執行程式碼

注意每次移動都會將低位右側補零。相對應,使用右移位元運算符進行運算時,每個位均向右方移動,空出的高位補零,如下範例 (有符號數除外,參考下面的算術移位註釋)。

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

練習場中可以執行範例。

可以利用左移和右移運算中,每次移動都表示一個數的 2 次冪這個特性,來作為某些乘法和除法運算的小技巧。例如,如下程式碼中,我們可以使用右移運算將 200(儲存在變數 a 中)除以 2 。

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

// 列印:
100

練習場 中可以執行範例。

或是通過左移 2 位,將一個數乘以4:

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

48

練習場 中可以執行範例。

位移運運算元提供了有趣的方式處理二進位制值中特定位置的值。例如,下列的程式碼中,|<< 用於設定變數 a 的第三個 bit 位。

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

可以在 練習場 中執行程式碼範例。

或者,您可以組合位移運運算元和 & 測試是否設定了第n位,如下面範例所示:

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

// 列印:
take action

練習場中執行程式碼。

使用 &^ 和位移運運算元,我們可以取消設定一個值的某個位。例如,下面的範例將變數 a 的第三位置為 0 :

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://dev.to/vladimirvivien/bit-hacking-with-go

更多程式設計相關知識,請存取:!!

以上就是一文詳解Golang中的位元運算的詳細內容,更多請關注TW511.COM其它相關文章!