这几天在研究 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 。
^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 &^ b
与 a & ^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 的语法都还没学完!