Go|Go 里面的 ^ 和 &^

这几天在研究 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 来指定位置。
例如
pos12345 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 里面的 ^ 和 &^】哭,用了快两年的 Go,居然连 Go 的语法都还没学完!

    推荐阅读