O.2 - 位运算符
Key Takeaway
按位运算符
C++ 提供 6 位操作运算符,通常称为按位运算符:
Operator | Symbol | Form | Operation |
---|---|---|---|
左移 | << | x << y | x 中的所有位左移 y 位 |
右移 | >> | x >> y | x 中的所有位右移 y 位 |
按位非 | ~ | ~x | x 中的所有位翻转 |
按位与 | & | x & y | x 中的每一位 AND y 中的每一位 |
按位或 | | | × | y | x 中的每一位 OR y 中的每一位 |
按位异或 | ^ | x^y | x 中的每一位 XOR y 中的每一位 |
作者注
在以下示例中,我们将主要使用 4 位二进制值。这是为了方便和保持示例简单。在实际程序中,使用的位数取决于对象的大小(例如,一个 2 字节的对象将存储 16 位)。 为了可读性,我们还将省略代码示例之外的 0b 前缀(例如,我们将只使用 0101 而不是 0b0101)。
按位运算符是为整数类型和 std::bitset 定义的。我们将在示例中使用 std::bitset,因为它更容易以二进制形式打印输出。
避免使用带符号操作数的按位运算符,因为在 C++20 之前,许多运算符将返回实现定义的结果,或有着使用无符号操作数(或 std::bitset)可以轻松避免的其他潜在问题。
最佳实践
为避免意外,请使用带无符号操作数的按位运算符或 std::bitset。
按位左移 (<<) 和按位右移 (>>) 运算符
按位左移(<<) 运算符将位向左移动。左操作数是要移位的位的表达式,右操作数是要左移的整数位数。
所以当我们说 时x << 1
,我们是在说“将变量 x 中的位左移一位”。从右侧移入的新位接收值 0。
0011 << 1 是 0110 0011 << 2 是 1100 0011 << 3 是 1000
请注意,在第三种情况下,我们将一个1从左端偏移出了!从二进制数末尾移出的位将永远丢失。
按位右移(>>) 运算符将位右移。
1100 >> 1 是 0110 1100 >> 2 是 0011 1100 >> 3 是 0001
请注意,在第三种情况下,我们将一个1从右端偏移出了,因此它丢失了。
这是进行一些位移的示例:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
这打印:
1 2 3 |
|
什么!?operator<< 和 operator>> 不是用于输入和输出吗?
他们肯定是。
今天的程序通常不会大量使用按位左移和右移运算符来移位。相反,您倾向于看到与 std::cout(或其他流对象)一起使用的按位左移运算符来输出文本。考虑以下程序:
1 2 3 4 5 6 7 8 9 10 11 |
|
这个程序打印:
1 |
|
在上面的程序中,operator<< 是如何知道在一种情况下移位而在另一种情况下输出x的呢?答案是 std::cout 已经重载(提供了替代定义) operator<< 执行控制台输出而不是位移。
当编译器看到 operator<< 的左操作数是 std::cout 时,它知道它应该调用 std::cout 重载的 operator<< 版本来进行输出。如果左操作数是整数类型,则 operator<< 知道它应该执行其通常的位移行为。
这同样适用于运算符>>。
请注意,如果您对输出和左移都使用运算符 <<,则需要括号:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
这打印:
1 2 |
|
第一行打印 x 的值 (0110),然后是文字 1。第二行打印 x 左移 1 (1100) 的值。
我们将在以后的部分中更多地讨论运算符重载,包括讨论如何为您自己的目的重载运算符。
按位非
按位非运算符 (~) 可能是所有按位运算符中最容易理解的。它只是将每个位从 0 翻转为 1,反之亦然。请注意,按位 NOT的结果取决于您的数据类型的大小。
翻转 4 位: ~0100 是 1011
翻转 8 位: ~0000 0100 是 1111 1011
在 4 位和 8 位的情况下,我们从相同的数字开始(二进制 0100 与 0000 0100 相同,十进制 7 与 07 相同),但最终得到不同的结果。
我们可以在以下程序中看到这一点:
1 2 3 4 5 6 7 8 9 |
|
```这打印: 这打印: 1011 11111011
1 2 3 4 5 |
|
1 2 3 |
|
0 1 1 1
1 2 3 4 5 6 7 8 9 10 11 12 |
|
这打印:
1 |
|
我们可以对复合 OR 表达式做同样的事情,例如0b0111 | 0b0011 | 0b0001
. 如果一列中的任何位为1,则该列的结果为1。
1 2 3 4 5 |
|
这是上面的代码:
1 2 3 4 5 6 7 8 9 |
|
这打印:
1 |
|
按位与
按位与(&) 的工作方式与上述类似。如果左右操作数的计算结果都为 true ,则逻辑 AND的计算结果为true。如果列中的两位都为1 ,则按位与计算结果为真 (1)。考虑表达式。排列每个位并对每一列位应用 AND 运算:0b0101 & 0b0110
1 2 3 4 |
|
1 2 3 4 5 6 7 8 |
|
这打印:
1 |
|
同样,我们可以对复合 AND 表达式做同样的事情,例如0b0001 & 0b0011 & 0b0111
. 如果一列中的所有位均为 1,则该列的结果为 1。
1 2 3 4 5 |
|
1 2 3 4 5 6 7 8 9 |
|
这打印:
1 |
|
按位异或
最后一个运算符是按位异或(^),也称为异或。
评估两个操作数时,如果 XOR 的一个且只有一个操作数为true (1) ,则 XOR 的计算结果为true (1)。如果两者都不为真,则计算结果为0。考虑表达式:0b0110 ^ 0b0011
1 2 3 4 |
|
也可以评估复合 XOR 表达式列样式,例如0b0001 ^ 0b0011 ^ 0b0111
. 如果一列中有偶数个 1 位,则结果为0。如果一列中有奇数个 1 位,则结果为1。
1 2 3 4 5 |
|
按位赋值运算符
与算术赋值运算符类似,C++ 提供了按位赋值运算符,以方便变量的修改。
Operator | Symbol | Form | Operation |
---|---|---|---|
左移分配 | <<= | x <<= y | x 左移 y 位 |
右移分配 | >>= | x >>= y | x 右移 y 位 |
按位或赋值 | |= | x |= y | 将 x | y 赋值给x |
按位与赋值 | &= | x &= y | 将 x & y 赋值给 x |
按位异或赋值 | ^= | x^=y | 将 x ^ y 赋值给 x |
例如x = x >> 1;
,您可以不写 ,而写x >>= 1;
.
1 2 3 4 5 6 7 8 9 10 11 |
|
这个程序打印:
1 |
|
概括
总结如何使用列方法按位运算:
在评估按位或时,如果列中的任何位为 1,则该列的结果为 1。 在评估按位与时,如果列中的所有位均为 1,则该列的结果为 1。 在评估按位异或时,如果一列中有奇数个 1 位,该列的结果为 1。