按位运算是直接操作数据二进制位的底层运算,在C++中提供了对整数类型进行位级操作的强大能力。掌握它,你就能写出更高效、更巧妙的代码。
一、六大按位运算符
1. 按位与 &
对应位都为1时结果为1,否则为0。
int a = 5; // 0101
int b = 3; // 0011
int c = a & b; // 0001,即1
用途:清零特定位、提取特定位、判断奇偶(n & 1)。
2. 按位或 |
对应位有一个为1结果就为1。
int a = 5; // 0101
int b = 3; // 0011
int c = a | b; // 0111,即7
用途:设置特定位为1。
3. 按位异或 ^
对应位不同时为1,相同时为0。
int a = 5; // 0101
int b = 3; // 0011
int c = a ^ b; // 0110,即6
特性:a ^ a = 0,a ^ 0 = a。用于交换两数、查找唯一出现一次的数字。
4. 按位取反 ~
0变1,1变0。
unsigned char a = 0x0F; // 00001111
unsigned char b = ~a; // 11110000,即0xF0
5. 左移 <<
二进制位向左移动,低位补0。
int a = 5; // 0101
int b = a << 2; // 010100,即20
相当于乘以2ⁿ(不溢出时)。
6. 右移 >>
二进制位向右移动,无符号数高位补0,有符号数通常补符号位。
unsigned int a = 20; // 10100
unsigned int b = a >> 2; // 00101,即5
相当于除以2ⁿ(向下取整)。
二、常见应用场景
1. 标志位管理
const int FLAG_A = 1 << 0; // 0001
const int FLAG_B = 1 << 1; // 0010
const int FLAG_C = 1 << 2; // 0100
int flags = FLAG_A | FLAG_B; // 设置A和B标志
if (flags & FLAG_B) { // 检查B标志
// B已设置
}
flags &= ~FLAG_A; // 清除A标志
2. 高效算法
判断是否为2的幂:
bool isPowerOfTwo(unsigned int n) {
return n != 0 && (n & (n - 1)) == 0;
}
统计二进制中1的个数:
int countOnes(unsigned int n) {
int count = 0;
while (n) {
n &= (n - 1); // 清除最低位的1
count++;
}
return count;
}
交换两个数(不使用临时变量):
void swap(int& a, int& b) {
a ^= b;
b ^= a;
a ^= b;
}
三、⚠️ 注意事项与陷阱
1. 运算符优先级陷阱
这是最容易犯的错误!
// ❌ 错误!== 的优先级高于 &
if (x & y == 0) { // 实际执行: x & (y == 0)
// 不是检查x和y没有公共位!
}
// ✅ 正确!必须加括号
if ((x & y) == 0) { // 正确检查x和y没有公共位
// 业务逻辑
}
比较运算符(==, !=, <, >等)的优先级高于位运算符(&, |, ^),但低于移位运算符(<<, >>)。
黄金法则:混合使用位运算和其他运算符时,务必使用括号明确优先级。
2. 有符号数的未定义行为
int a = 1 << 31; // 32位int:未定义行为!
int b = -1 >> 1; // 右移负数:实现定义,不同编译器结果可能不同
// 建议使用无符号类型进行位运算
unsigned int safe_a = 1U << 31; // 明确的行为
3. 移位越界
unsigned int x = 1;
unsigned int y = x << 32; // 未定义行为(移位位数≥类型位数)
// 安全做法
template<typename T>
T safe_shift(T value, unsigned shift) {
if (shift >= sizeof(T) * CHAR_BIT) return 0;
return value << shift;
}
四、C++20的增强
C++20引入了<bit>头文件,提供了跨平台的位操作:
#include <bit>
#include <cstdint>
uint32_t x = 0x12345678;
std::popcount(x); // 统计1的个数
std::has_single_bit(x); // 是否只有一个1
std::bit_width(x); // 表示x所需的最小位数
std::byteswap(x); // 字节序转换(C++23)
五、实用技巧
1. 提取/设置特定位
// 提取第3位(从0开始)
bool bit3 = (value >> 3) & 1;
// 设置第5位为1
value |= (1 << 5);
// 设置第5位为0
value &= ~(1 << 5);
// 切换第5位
value ^= (1 << 5);
2. 快速乘除2的幂
unsigned int a = 42;
unsigned int b = a << 3; // 乘以8
unsigned int c = a >> 2; // 除以4
3. 位集合实现
class Bitset32 {
private:
uint32_t data = 0;
public:
void set(size_t pos) { data |= (1U << pos); }
void clear(size_t pos) { data &= ~(1U << pos); }
bool test(size_t pos) { return (data >> pos) & 1U; }
void toggle(size_t pos){ data ^= (1U << pos); }
};
总结
按位运算让我们能够以最直接的方式操作数据,在性能关键场景下非常有用。记住几个关键点:
- 优先级陷阱:位运算符优先级低于比较运算符,务必加括号
- 类型安全:尽量用无符号类型进行位运算,避免未定义行为
- 边界检查:移位操作要确保不越界
- 代码可读性:复杂位运算要适当注释
虽然现代编译器优化能力很强,但在需要极致性能或进行底层编程时,位运算仍然是不可或缺的工具。掌握它,你就能写出更高效、更优雅的代码。
(注:示例代码中省略了必要的边界检查,实际使用时应根据需求添加。)