在電腦記憶體昂貴,處理能力有限的美好舊時光裡,用比較黑客範的位元運算方式去處理資訊是首選方式(某些情況下只能如此)。時至今日,直接使用位元運算仍然是許多計算領域中不可或缺的部分,例如底層系統編程,圖形處理,密碼學等。
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 中確實都存在。所以我們可以結合使用
OR 和
AND 運算的方式來分別設定和讀取某位的配置值。 .
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 語句區塊來取得這些位元操作進行對應的字串轉換。
異或 運算子是用
^ 來表示的。
異或運算子有如下的特點:
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(&amp;amp;amp;quot;a and b have same sign?&amp;amp;amp;quot;, (a ^ b) &amp;amp;amp;gt;= 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(&amp;amp;amp;quot;%08b\n&amp;amp;amp;quot;, a) fmt.Printf(&amp;amp;amp;quot;%08b\n&amp;amp;amp;quot;, ^a) } // 打印结果 00001111 // var a 11110000 // ^a
在练习场中可以运行范例。
&amp;amp;amp;^
操作符意为 与非
,是 与
和 非
操作符的简写形式,它们定义如下。
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(&amp;amp;amp;quot;%08b\n&amp;amp;amp;quot;, a) a &amp;amp;amp;amp;^= 0x0F fmt.Printf(&amp;amp;amp;quot;%08b\n&amp;amp;amp;quot;, a) } // 打印: 10101011 10100000
在练习场中运行范例。
与其他 C 的衍生语言类似, Go 使用 <<
和 来表示左移运算符和右移运算符,如下所示:
Given integer operands a and n, a &amp;amp;amp;lt;&amp;amp;amp;lt; n; shifts all bits in a to the left n times a &amp;amp;amp;gt;&amp;amp;amp;gt; n; shifts all bits in a to the right n times
例如,在下面的代码片段中变量 a
(00000011
)的值将会左移位运算符分别移动三次。每次输出结果都是为了说明左移的目的。
func main() { var a int8 = 3 fmt.Printf(&amp;amp;amp;quot;%08b\n&amp;amp;amp;quot;, a) fmt.Printf(&amp;amp;amp;quot;%08b\n&amp;amp;amp;quot;, a&amp;amp;amp;lt;&amp;amp;amp;lt;1) fmt.Printf(&amp;amp;amp;quot;%08b\n&amp;amp;amp;quot;, a&amp;amp;amp;lt;&amp;amp;amp;lt;2) fmt.Printf(&amp;amp;amp;quot;%08b\n&amp;amp;amp;quot;, a&amp;amp;amp;lt;&amp;amp;amp;lt;3) } // 输出的结果: 00000011 00000110 00001100 00011000
在 Playground 运行代码
注意每次移动都会将低位右侧补零。相对应,使用右移位操作符进行运算时,每个位均向右方移动,空出的高位补零,如下示例 (有符号数除外,参考下面的算术移位注释)。
func main() { var a uint8 = 120 fmt.Printf(&amp;amp;amp;quot;%08b\n&amp;amp;amp;quot;, a) fmt.Printf(&amp;amp;amp;quot;%08b\n&amp;amp;amp;quot;, a&amp;amp;amp;gt;&amp;amp;amp;gt;1) fmt.Printf(&amp;amp;amp;quot;%08b\n&amp;amp;amp;quot;, a&amp;amp;amp;gt;&amp;amp;amp;gt;2) } // 打印: 01111000 00111100 00011110
在 练习场中可以运行范例。
可以利用左移和右移运算中,每次移动都表示一个数的 2 次幂这个特性,来作为某些乘法和除法运算的小技巧。例如,如下代码中,我们可以使用右移运算将 200
(存储在变量 a 中)除以 2 。
func main() { a := 200 fmt.Printf(&amp;amp;amp;quot;%d\n&amp;amp;amp;quot;, a&amp;amp;amp;gt;&amp;amp;amp;gt;1) } // 打印: 100
在 练习场 中可以运行范例。
或是通过左移 2 位,将一个数乘以4:
func main() { a := 12 fmt.Printf(&amp;amp;amp;quot;%d\n&amp;amp;amp;quot;, a&amp;amp;amp;lt;&amp;amp;amp;lt;2) } // 打印: 48
在 练习场 中可以运行范例。
位移运算符提供了有趣的方式处理二进制值中特定位置的值。例如,下列的代码中,|
和 <<
用于设置变量 a
的第三个 bit 位。
func main() { var a int8 = 8 fmt.Printf(&amp;amp;amp;quot;%08b\n&amp;amp;amp;quot;, a) a = a | (1&amp;amp;amp;lt;&amp;amp;amp;lt;2) fmt.Printf(&amp;amp;amp;quot;%08b\n&amp;amp;amp;quot;, a) } // prints: 00001000 00001100
可以在 练习场 中运行代码示例。
或者,您可以组合位移运算符和 &amp;amp;amp;
测试是否设置了第n位,如下面示例所示:
func main() { var a int8 = 12 if a&amp;amp;amp;amp;(1&amp;amp;amp;lt;&amp;amp;amp;lt;2) != 0 { fmt.Println(&amp;amp;amp;quot;take action&amp;amp;amp;quot;) } } // 打印: take action
在 练习场中运行代码。
使用 &amp;amp;amp;^
和位移运算符,我们可以取消设置一个值的某个位。例如,下面的示例将变量 a 的第三位置为 0 :
func main() { var a int8 = 13 fmt.Printf(&amp;amp;amp;quot;%04b\n&amp;amp;amp;quot;, a) a = a &amp;amp;amp;amp;^ (1 &amp;amp;amp;lt;&amp;amp;amp;lt; 2) fmt.Printf(&amp;amp;amp;quot;%04b\n&amp;amp;amp;quot;, 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
翻譯網址:https://learnku.com/go/t/ 23460/bit-operation-of-go
#推薦學習:Golang教程
以上是淺析Golang中的的位元操作(位元運算子)的詳細內容。更多資訊請關注PHP中文網其他相關文章!