位运算符(选读)
位运算符包含以下六种:
| 运算符 | 名称 | 作用 |
|---|---|---|
a & b | 逐位与运算符 | 两侧操作数逐位进行与运算 |
a | b | 逐位或运算符 | 两侧操作数逐位进行或运算 |
a ^ b | 逐位异或运算符 | 两侧操作数诸位进行异或运算 |
~a | 取反运算符 | 将操作数逐位取反 |
a << b | 左移运算符 | 将左侧操作数内存左移 |
a >> b | 右移运算符 | 将左侧操作数内存右移 |
下面我们来分别介绍它们。
逐位与运算符
逐位与表达式的写法如下:
左侧操作数 & 右侧操作数
将参加运算的两个操作数的每个二进制位进行独立的与运算。若类型不同,进行如同算术运算一样的类型转换。例如 3 & 5 ,即 0b011 & 0b101 (之前提到过, 0b 前缀代表二进制。),它的运算过程是这样的:
得到的结果为 1 。
逐位或运算符
逐位或表达式的写法如下:
左侧操作数 | 右侧操作数
将参加运算的两个操作数的每个二进制位进行独立的或运算。若类型不同,进行如同算术运算一样的类型转换。例如 3 | 5 ,即 0b011 | 0b101 ,它的运算过程是这样的:
得到的结果为 7 。
逐位异或运算符
异或运算在日常生活中貌似比较少见。以下是异或运算的结果:
| XOR(异或) | 1 | 0 |
|---|---|---|
1 | 0 | 1 |
0 | 1 | 0 |
异或运算的作用其实非常大。你可以阅览这份 知乎回答 来获取更直观的理解。
可以看出,异或即两位是否不同。逐位异或表达式的写法如下:
左侧操作数 ^ 右侧操作数
将参加运算的两个操作数的每个二进制位进行独立的异或运算。若类型不同,进行如同算术运算一样的类型转换。例如 57 ^ 42 即 0b111001 ^ 0b101010 ,它的运算过程是这样的:
(其中 是异或的数学记号)得到的结果为 19 。
取反运算符
取反表达式的写法如下:
~操作数
将操作数的二进制表示逐位取反。即 1 变为 0 , 0 变为 1 。例如:对于
则 ~us 的值为 0b1111111111101010 ,即 65514 。
左移运算符和右移运算符
左移表达式和右移表达式的写法如下:
左侧操作数 << 右侧操作数 左侧操作数 >> 右侧操作数
其作用为求出将左侧操作数的各个二进制位向左/右移动右侧操作数那么多位的值。其中,左侧操作数将进行整型提升(参见算术运算符章节注释)。右侧操作数需为非负数。(若右侧操作数为负数,则行为未定义。)
对于左移运算,左侧溢出部分舍去,右侧用 0 补齐;对于右移运算,右侧溢出部分舍去,左侧采用如下的办法补齐:
- 若原数无符号,则用
0补齐; - 若原数有符号,非负数用
0补齐,负数用1补齐。
左移和右移运算可简单地概括为:左移运算左侧操作数乘 并舍去溢出部分,右移运算左侧操作数除以 并向零取整。其中 为右侧操作数的值。例如:
42 << 2即0b101010 << 2,结果为0b10101000,即 ;42 >> 2即0b101010 >> 2,结果为0b1010,即 。(中括号为取整(高斯)函数。)
复合赋值运算符的扩充
位运算符也可以与赋值运算相结合。其含义与我们学过的复合赋值运算符相仿。
| 运算符 | 名称 | 作用 |
|---|---|---|
a &= b | 逐位与复合赋值运算符 | 逐位与后赋值给左侧数 |
a |= b | 逐位或复合赋值运算符 | 逐位或后赋值给左侧数 |
a ^= b | 逐位异或复合赋值运算符 | 逐位异或后赋值给左侧数 |
a <<= b | 左移复合赋值运算符 | 左移后赋值给左侧数 |
a >>= b | 右移复合赋值运算符 | 右移后赋值给左侧数 |
这些符合赋值表达式的写法为:
左侧数 位运算符= 右侧数
它等价于
左侧数 = 左侧数 位运算符 右侧数
位运算的作用
由于位运算在计算机中的运行速度最快,因此使用带有位运算的技巧可以显著提升程序效率,但相应的可能会有可读性的损失。
获取某变量的某二进制位
我想知道 int 型变量 a 的第 x 位是 0 还是 1 (最低位为第 0 位),可以用这个表达式:
bool(a & (1 << x))
这是因为, 1 << x 会得到一个仅第 x 位为 1 ,而其余位全为 0 的值。然后它去和 a 做“与”运算,其它位全部归零,只有第 x 位被保留。因此,若第 x 位为 0 ,则上述表达式结果为 false ,否则为 true 。
还有一种写法
1 & (a >> x)也可得到一样的结果,而且可以不用转型。请读者自行推断这个表达式的含义。
快速交换变量
若要交换 a 和 b 两个变量的值,可以使用:
a ^= b;
b ^= a;
a ^= b;
这样的写法而不借助第三个变量。具体的原理是:
异或具有交换律和结合律(请读者自行验证),且对于任意数 显然满足以下特性:
- ;
- 。
我们用 和 表示运算前 a 和 b 的值,则
- 第一行执行后,
a的值为 ,b的值为 ; - 第二行执行后,
a的值为 ,b的值为 ; - 第三行执行后,
a的值为 ,b的值为 。
这时, a 的值
而 b 的值
这就证明了交换的正确性。
不过不建议在程序中使用这样的写法,因为可读性较差且有致命的陷阱:若 a 和 b 指代同一变量时,该方法将失效。(相当于做了三次 a ^= a ,结果 a 被清零。)