位运算符(选读)

位运算符包含以下六种:

运算符名称作用
a & b逐位与运算符两侧操作数逐位进行与运算
a | b逐位或运算符两侧操作数逐位进行或运算
a ^ b逐位异或运算符两侧操作数诸位进行异或运算
~a取反运算符将操作数逐位取反
a << b左移运算符将左侧操作数内存左移
a >> b右移运算符将左侧操作数内存右移

下面我们来分别介绍它们。

逐位与运算符

逐位与表达式的写法如下:

左侧操作数 & 右侧操作数

将参加运算的两个操作数的每个二进制位进行独立的与运算。若类型不同,进行如同算术运算一样的类型转换。例如 3 & 5 ,即 0b011 & 0b101 (之前提到过, 0b 前缀代表二进制。),它的运算过程是这样的:

 (011)2&(101)2 (001)2 \begin{array}{cc} \ &(011)_2\\ \&&(101)_2\\ \hline \ &(001)_2 \end{array}

得到的结果为 1

逐位或运算符

逐位或表达式的写法如下:

左侧操作数 | 右侧操作数

将参加运算的两个操作数的每个二进制位进行独立的或运算。若类型不同,进行如同算术运算一样的类型转换。例如 3 | 5 ,即 0b011 | 0b101 ,它的运算过程是这样的:

 (011)2(101)2 (111)2 \begin{array}{cc} \ &(011)_2\\ |&(101)_2\\ \hline \ &(111)_2 \end{array}

得到的结果为 7

逐位异或运算符

异或运算在日常生活中貌似比较少见。以下是异或运算的结果:

XOR(异或)10
101
010

异或运算的作用其实非常大。你可以阅览这份 知乎回答open in new window 来获取更直观的理解。

可以看出,异或即两位是否不同。逐位异或表达式的写法如下:

左侧操作数 ^ 右侧操作数

将参加运算的两个操作数的每个二进制位进行独立的异或运算。若类型不同,进行如同算术运算一样的类型转换。例如 57 ^ 420b111001 ^ 0b101010 ,它的运算过程是这样的:

 (111001)2(101010)2 (010011)2 \begin{array}{cc} \ &(111001)_2\\ \oplus&(101010)_2\\ \hline \ &(010011)_2 \end{array}

(其中 \oplus 是异或的数学记号)得到的结果为 19

取反运算符

取反表达式的写法如下:

~操作数

将操作数的二进制表示逐位取反。即 1 变为 0 , 0 变为 1 。例如:对于

unsigned short us{0b10101}; // 十进制为 21

~us 的值为 0b1111111111101010 ,即 65514

左移运算符和右移运算符

左移表达式和右移表达式的写法如下:

左侧操作数 << 右侧操作数
左侧操作数 >> 右侧操作数

其作用为求出将左侧操作数的各个二进制位向左/右移动右侧操作数那么多位的值。其中,左侧操作数将进行整型提升(参见算术运算符章节注释)。右侧操作数需为非负数。(若右侧操作数为负数,则行为未定义。)

对于左移运算,左侧溢出部分舍去,右侧用 0 补齐;对于右移运算,右侧溢出部分舍去,左侧采用如下的办法补齐:

  • 若原数无符号,则用 0 补齐;
  • 若原数有符号,非负数用 0 补齐,负数用 1 补齐。

左移和右移运算可简单地概括为:左移运算左侧操作数乘 2n2^n 并舍去溢出部分,右移运算左侧操作数除以 2n2^n 并向零取整。其中 2n2^n 为右侧操作数的值。例如:

  • 42 << 20b101010 << 2 ,结果为 0b10101000 ,即 168=42×22168=42\times2^2
  • 42 >> 20b101010 >> 2 ,结果为 0b1010 ,即 10=[4222]10=\left[\frac{42}{2^2}\right] 。(中括号为取整(高斯)函数。)

复合赋值运算符的扩充

位运算符也可以与赋值运算相结合。其含义与我们学过的复合赋值运算符相仿。

运算符名称作用
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) 也可得到一样的结果,而且可以不用转型。请读者自行推断这个表达式的含义。

快速交换变量

若要交换 ab 两个变量的值,可以使用:

a ^= b;
b ^= a;
a ^= b;

这样的写法而不借助第三个变量。具体的原理是:

异或具有交换律和结合律(请读者自行验证),且对于任意数 xx 显然满足以下特性:

  • xx=0x\oplus x=0
  • x0=xx\oplus0=x

我们用 a0a_0b0b_0表示运算前 ab 的值,则

  • 第一行执行后, a 的值为 a0b0a_0\oplus b_0b 的值为 b0b_0
  • 第二行执行后, a 的值为 a0b0a_0\oplus b_0b 的值为 b0a0b0b_0\oplus a_0\oplus b_0
  • 第三行执行后, a 的值为 a0b0b0a0b0a_0\oplus b_0\oplus b_0\oplus a_0\oplus b_0b 的值为 b0a0b0b_0\oplus a_0\oplus b_0

这时, a 的值

a0b0b0a0b0=a0(b0b0)a0b0=a00a0b0=a0a0b0=0b0=b0 \begin{aligned} &a_0\oplus b_0\oplus b_0\oplus a_0\oplus b_0\\ =&a_0\oplus (b_0\oplus b_0)\oplus a_0\oplus b_0\\ =&a_0\oplus0\oplus a_0\oplus b_0\\ =&a_0\oplus a_0\oplus b_0\\ =&0\oplus b_0\\ =&b_0 \end{aligned}

b 的值

b0a0b0=b0b0a0=0a0=a0 \begin{aligned} &b_0\oplus a_0\oplus b_0\\ =&b_0\oplus b_0\oplus a_0\\ =&0\oplus a_0\\ =&a_0 \end{aligned}

这就证明了交换的正确性。

不过不建议在程序中使用这样的写法,因为可读性较差且有致命的陷阱:若 ab 指代同一变量时,该方法将失效。(相当于做了三次 a ^= a ,结果 a 被清零。)

最近更新:
代码未运行