Go 裡面的 ^ 和 &^

機智的小小帥發表於2022-04-02

這幾天在研究 Go 的原始碼,突然發現了一個之前沒有見過的位運算,見這裡

new &^= mutexWoken

&^,分別表示 AND 和 XOR,這個不用多說。

值得一提的是 ^ 這個符號,在我的印象中,它一直是一個二元運算子,平時見的最多的是 a ^ b 這種用法。

但是實際上它還是一個一元運算子。單走一個 a 也是沒問題的,例如 ^a

^ 作為一元運算子的作用

去知識的源頭尋找答案!

在 Go 的規範文件Constant expressions 這一節中有提到 ^ 作為一元運算子的作用

The mask used by the unary bitwise complement operator ^ matches the rule for non-constants: the mask is all 1s for unsigned constants and -1 for signed and untyped constants.

^1         // untyped integer constant, equal to -2
uint8(^1)  // illegal: same as uint8(-2), -2 cannot be represented as a uint8
^uint8(1)  // typed uint8 constant, same as 0xFF ^ uint8(1) = uint8(0xFE)
int8(^1)   // same as int8(-2)
^int8(1)   // same as -1 ^ int8(1) = -2

^a: 當 a 是 unsigned 時,相當於用 11111... (... 表示很多很多 1) 與 a 做異或運算;當 a 是 signed 時,相當於用 -1與 a 做異或運算

PS: 無論 int 還是 uint ,其底層都是用 bit 表示的,而 -1 用補碼錶示就是 1111... ,如果從 bit 的角度出現,可以發現,無論 a 是正數還是負數最終都是與 1111... 做 XOR 運算!而最終的效果則是將 a 所有的 bit 位的值全部反轉

讓我們來推導一下,首先複習一下反碼和補碼的知識:

反碼: 正數的反碼等於本身,負數的反碼保持符號位不變,其他位取反

補碼: 正數的補碼等於它本身,負數的補碼等於它的反碼加一

下面為了表示方便,正數和負數用到 8 位 bit 表示,即 int8 和 uint8 。

型別 原碼 反碼 補碼
int8 1 0000 0001 0000 0001 0000 0001
int8 -1 1000 0001 1111 1110 1111
uint8 1 0000 0001 0000 0001 0000 0001
int8 -2 1000 0010 1101 1111 1110
uint8 254 (255 - 1) 1111 1110 1111 1110 1111 1110
^1         // untyped integer constant,使用 -1 與其做 XOR 運算 1111 1111 ^ 0000 0001 = 1111 1110, 恰好為 -2 的補碼
uint8(^1)  // illegal: same as uint8(-2), -2 cannot be represented as a uint8
^uint8(1)  // typed uint8 constant, same as 0xFF ^ uint8(1) = uint8(0xFE)
int8(^1)   // same as int8(-2)
^int8(1)   // same as -1 ^ int8(1) = -2
&^ 的作用

回到 &^ 上面來,在 Arithmetic_operators 這一節中記錄了 &^ 這個運算子。

&^   bit clear (AND NOT)    integers

實際上 a &^ b 的效果近似於 a & (^b),即將 ^b 的結果與 a 做 AND 運算。

&^ 名為 bit clear,他的作用自然也就是 bit clear,a &^ mask 會將 a 中一些位置的 bit 值 clear 為 0,通過將 mask 中 bit 值設定為 1 來指定位置。

例如

pos    12345
a    = 11001
mask = 01010

mask 的第 2 和第 4 位的 bit 為 1,則意味著將 a 的第 2 位和第 4 位的 bit clear 為 0,因此 a &^ mask 的結果為 10001

證明過程也很簡單,之前說過 ^mask 的結果是將 mask 的 bit 位全部取反,所以 mask 內原本為 1 的 bit 就變成了 0,之後再與 a 做 AND 運算,任何 bit 值與 0 做 AND 運算的結果都為 0。

a &^ ba & ^b

上面提到過 a &^ b 的效果近似於 a & (^b),但是它兩還是有一點微笑區別的。具體可見 stackoverflow 的這個問題: Why does Go have a "bit clear (AND NOT)" operator?

There's a subtle difference that makes dealing with literals and untyped constants easier with the explicit bit clear operator.

Untyped integers have their default type as int so something like a := uint32(1) & ^1 is illegal as ^1 is evaluated first and it's evaluated as ^int(1), which equals -2. a := uint32(1) &^ 1 is legal however as here 1 is evaluated as uint32, based on the context.

There could also be some performance gains in having an explicit bit clear, but I'm not too sure about that.

感觸

哭,用了快兩年的 Go,居然連 Go 的語法都還沒學完!

相關文章