什么是C++位运算符?
C++ 提供了 6 个用于位操作的运算符,通常称为位运算符:
运算符名称 | 符号 | 形式 | 运算结果 |
---|---|---|---|
左移 | « | x « n | 将 x 的各位左移 n 位,右侧补 0 |
右移 | » | x » n | 将 x 的各位右移 n 位,左侧补 0 |
按位取反 | ~ | ~x | 将 x 的每一位取反 |
按位与 | & | x & y | 仅当 x 与 y 对应位均为 1 时,结果位为 1 |
按位或 | | | x | y | 只要 x 或 y 对应位中有一个为 1,结果位即为 1 |
按位异或 | ^ | x ^ y | 当 x 与 y 对应位不同时,结果位为 1 |
上述运算符均为非修改型,即不会更改其操作数的值。
作者注
为便于说明,下文示例大多采用 4 位二进制值。实际程序中,位数取决于对象大小(如 2 字节对象将存储 16 位)。为提高可读性,代码示例之外的二进制字面量可能省略前缀 0b(如 0101 即 0b0101)。
位运算符适用于所有整型及 std::bitset。以下示例使用 std::bitset,因其二进制输出更直观。
切勿将位运算符用于有符号整型操作数;在 C++20 之前,许多运算结果由实现定义,或存在其他隐患。请改用无符号整型或 std::bitset。
最佳实践
为避免意外,请使用无符号整型或 std::bitset 进行位运算。
C++左移(«)与右移(»)运算符
左移运算符将位向左移动。左操作数为初始位序列,右操作数为移动位数。例如 x << 2
表示“生成一个新值,其位序列为 x 左移 2 位”。左操作数不变,右侧新位补 0。
示例:对 0011 进行左移
0011 << 1 → 0110
0011 << 2 → 1100
0011 << 3 → 1000(注意:第 3 次左移时最高位 1 被移出,该位永久丢失。)
右移运算符类似,但方向向右。
示例:对 1100 右移
1100 >> 1 → 0110
1100 >> 2 → 0011
1100 >> 3 → 0001(最低位被移出并丢失。)
C++ 示例(可编译运行)
#include <bitset>
#include <iostream>
int main()
{
std::bitset<4> x{ 0b1100 };
std::cout << x << '\n';
std::cout << (x >> 1) << '\n'; // 右移 1 位 → 0110
std::cout << (x << 1) << '\n'; // 左移 1 位 → 1000
return 0;
}
输出:
1100
0110
1000
进阶说明
C++ 的位移与字节序无关:左移始终向最高位移动,右移向最低位移动。
C++输入输出中的 « 和 »
疑问:运算符 « 和 » 不是用于输入/输出吗?
确实如此。
现代程序很少直接用于位移,更常见的是与 std::cout
等输出流配合输出文本。例如:
#include <bitset>
#include <iostream>
int main()
{
unsigned int x{ 0b0100 };
x = x << 1; // 使用 << 进行左移
std::cout << std::bitset<4>{ x } << '\n'; // 使用 << 输出
return 0;
}
输出:
1000
编译器通过操作数类型决定行为:若左操作数为整型,则执行位移;若为输出流对象,则执行输出。
此能力依赖“运算符重载”,后续 13.5 课将介绍。
注意:若同一表达式中既用于左移又用于输出,需加括号:
std::cout << x << 1 << '\n'; // 先输出 x,再输出 1
std::cout << (x << 1) << '\n'; // 输出 x 左移 1 位结果
C++按位取反(~)运算符
按位取反运算符将每一位取反:
- ~0011 → 1100
- ~0000 0100 → 1111 1011
进阶说明
将取反结果视为整数时,位数不同导致值不同。示例:
#include <bitset>
#include <iostream>
int main()
{
std::bitset<4> b4{ 0b100 }; // 0100
std::bitset<8> b8{ 0b100 }; // 00000100
std::cout << "初始值:\n";
std::cout << "Bits: " << b4 << ' ' << b8 << '\n';
std::cout << "Values:" << b4.to_ulong() << ' ' << b8.to_ulong() << "\n\n";
b4 = ~b4; // 1011
b8 = ~b8; // 11111011
std::cout << "取反后:\n";
std::cout << "Bits: " << b4 << ' ' << b8 << '\n';
std::cout << "Values:" << b4.to_ulong() << ' ' << b8.to_ulong() << '\n';
return 0;
}
输出:
初始值:
Bits: 0100 00000100
Values:4 4
取反后:
Bits: 1011 11111011
Values:11 251
C++按位或(|)运算符
按位或运算与逻辑或类似,但应用于每一位。若对应位中任一(或两者)为 1,结果位为 1。
示例:0b0101 | 0b0110
0 1 0 1 OR
0 1 1 0
---------
0 1 1 1
C++ 代码:
std::cout << (std::bitset<4>{0b0101} | std::bitset<4>{0b0110}) << '\n';
// 输出 0111
多操作数示例:
0 1 1 1 OR
0 0 1 1 OR
0 0 0 1
---------
0 1 1 1
C++按位与(&)运算符
按位与运算仅在两对应位均为 1 时结果位为 1。
示例:0b0101 & 0b0110 → 0100
多操作数示例:
0 0 0 1 AND
0 0 1 1 AND
0 1 1 1
---------
0 0 0 1
C++按位异或(^)运算符
按位异或(^)又称“排他或”。对应位不同时结果位为 1。
示例:0b0110 ^ 0b0011 → 0101
多操作数示例:
0 0 0 1 XOR
0 0 1 1 XOR
0 1 1 1
---------
0 1 0 1
C++位赋值运算符
与算术赋值运算符类似,C++ 提供位赋值运算符,这些运算符会修改左操作数:
运算符 | 符号 | 形式 | 作用 |
---|---|---|---|
左移赋值 | «= | x «= n | 左移 n 位,结果写回 x |
右移赋值 | »= | x »= n | 右移 n 位,结果写回 x |
与赋值 | &= | x &= y | 按位与结果写回 x |
或赋值 | |= | x |= y | 按位或结果写回 x |
异或赋值 | ^= | x ^= y | 按位异或结果写回 x |
例如,可写 x >>= 1;
代替 x = x >> 1;
示例:
std::bitset<4> bits{ 0b0100 };
bits >>= 1;
std::cout << bits << '\n'; // 输出 0010
注意:无按位取反赋值运算符,因按位取反为单目运算。若需翻转所有位,可使用普通赋值:x = ~x;
C++位运算与整型提升(进阶)
当操作数整型宽度小于 int
时,会被提升为 int
或 unsigned int
,结果亦为 int
或 unsigned int
。
相关说明见 10.2 课。
需特别注意两种情况:
operator~
与operator<<
对宽度敏感,结果可能因操作数宽度而异。- 将结果初始化或赋值给较小整型为窄化转换,列表初始化禁止,普通赋值可能警告。
示例(假设 32 位 int):
std::uint8_t c{ 0b00001111 };
std::cout << std::bitset<32>(~c) << '\n'; // 错误:输出 111...10000
std::cout << std::bitset<32>(c << 6) << '\n'; // 错误:输出 111...000000
std::uint8_t cneg{ ~c }; // 错误:窄化转换
c = ~c; // 可能警告
修正:使用 static_cast
转回目标类型:
std::cout << std::bitset<32>(static_cast<std::uint8_t>(~c)) << '\n';
std::cout << std::bitset<32>(static_cast<std::uint8_t>(c << 6)) << '\n';
std::uint8_t cneg{ static_cast<std::uint8_t>(~c) };
c = static_cast<std::uint8_t>(~c);
警告
位运算符会将小于 int
的整型提升为 int
/unsigned int
。operator~
与 operator<<
对宽度敏感,使用前务必将结果 static_cast
回目标类型。
最佳实践
尽量避免对小于 int
的整型进行位移操作。
C++位运算小结
使用列式法总结位运算:
- 按位或:列中任一为 1,则结果为 1。
- 按位与:列中全为 1,则结果为 1。
- 按位异或:列中 1 的个数为奇数,则结果为 1。
预告:C++位掩码与进阶技巧
下一课将探讨如何结合位掩码进行位操作。
C++位运算符测验与面试题
问题 1
- a) 0110 » 2 的二进制结果?
- b) 0011 | 0101 的二进制结果?
- c) 0011 & 0101 的二进制结果?
- d) (0011 | 0101) & 1001 的二进制结果?
问题 2
位旋转(rotate)与位移类似,但移出的位会绕回另一端。例如 0b1001 « 1 得 0b0010,而左旋转 1 位得 0b0011。请实现一个对 std::bitset<4>
的左旋转函数 rotl
,可使用 test()
与 set()
。
问题 3(加分)
不使用 test()
与 set()
,仅用位运算符重做问题 2。