位运算深度指南:让 CPU「直接对话」的二进制魔法

·

在现代编程里,位运算常被贴上「高深」「晦涩」的标签,然而它们本质上不过是 CPU 与内存最直接的对话方式。当你写下一行 a << 1 时,实际上在命令处理器「把这条二进制数整体左移一位」,其执行效率比乘法指令高出数个数量级。本文将带你从零拆解六大经典位运算,解锁高效代码算法加速底层优化的隐藏技巧。

位运算六维透视

1. 按位与(&):1+1=1,其余为 0

用于清零指定位提取掩码

uint8_t flags = 0b10101100;
if (flags & 0b00001000) { // 检查第四 bit 是否为 1
    // do something
}

2. 按位或(|):一球进网就得分

用于置位合并多标志位

flags |= 0b00000010; // 把第 2 位强行拉成 1

3. 按位异或(^):不同则真

快速交换变量的经典场景即用 XOR

x ^= y; y ^= x; x ^= y; // x 与 y 完成无临时变量交换

4. 按位取反(~):0变1,1变0

常结合掩码清除某些位

uint32_t clearUpper16 = value & ~0xFFFF0000;

5. 左移(<<):低位补零,等效乘 2ⁿ

int quickMul8 = num << 3; // 比 num * 8 更快

6. 右移(>>):逻辑右移高位补零,算术右移高位补符号

👉 用一条指令让数组长度减半
右移 1 位常用于二分查找中的中值计算,避免溢出的同时保持高性能。


实战场景:让位运算解决日常难题

校验奇偶:一行代码称霸

boolean isEven(int n) { return (n & 1) == 0; }

利用最低位即可秒杀 % 2 运算。

压缩存储:把 32 个布尔塞进一个 int

// 设置第 k 个开关
void turnOn(uint32_t *reg, int k) { *reg |= 1u << k; }

// 读取第 k 个开关
bool isOn(uint32_t reg, int k)  { return reg & (1u << k); }

在嵌入式或游戏引擎中,此法节省大量缓存行。

判断 2 的幂次

bool powerOfTwo(int n) { return n > 0 && (n & (n - 1)) == 0; }

分析:唯一让 n & (n-1) == 0 的二进制特征即为单 1 后跟全 0。


进阶技巧:脑爆级算法黑科技

Kernighan 算法:统计 1 的数量

int popcount(unsigned int v) {
    int cnt = 0;
    while (v) { v &= v - 1; cnt++; }
    return cnt;
}

每次循环至少干掉最低位的 1,位运算优化后经久不衰。

快速取右端首 1

int lowbit(int v) { return v & -v; }

常用于树状数组(Fenwick)维护前缀和。

无符号灰码转换

uint32_t toGray(uint32_t n) { return n ^ (n >> 1); }

相邻值仅差一位,旋转编码器与磁盘控制器的最爱。


各主流语言速查表

语言示例备注
C/C++a & b, a << 3直接映射 CPU 指令
Pythona ^ bint 无限精度,注意负数补码
Java~asigned >>> unsigned 右移需 >>>
JavaScripta >>> 0隐式 32-bit 转换;>>> 无符号

性能 VS 可读性:理智选择


常见坑与最佳实践

  1. 右移符号扩展:在 C 里对 -8 >> 1 结果留心编译器实现差异。
  2. 移位溢出:STM32 上 1 << 31 属 UB(Undefined Behavior)需特别处理。
  3. 括号优先级:a & b == 0 编译器解析为 a & (b == 0),建议显式括号。
实践贴士:给掩码起宏或常量名,#define FLAG_READ 0x0001,更易维护。

FAQ:那些第一眼懵的问题

Q1:为什么用位运算比乘法快?
现代 CPU 的移位指令只需一个时钟周期,而乘法器可能需要 3-5 周期,且占用更多晶体管资源。

Q2:Python int 无限大,用位运算还划算吗?
虽然数值无上限,但置换掩码或压缩布尔数组时依旧高效;大规模循环中可减少内存访问。

Q3:如何判断系统是大端还是小端?
借助 union 或指针强制转换读取首字节即可:

union { uint32_t i; uint8_t c[4]; } x = {0x01020304};
return x.c[0] == 0x04 ? 1 : 0;

Q4:能否把浮点数也用位运算加速?
不能直接移位;但可把 float 的 IEEE-754 表示转成 uint32_t 再做 bit_cast,例如在 Unity Burst 里手写 SIMD。

Q5:为什么不能 x << -3
C/C++ 标准规定移位量必须非负且小于位宽,否则行为未定义;建议封装安全函数。

Q6:位运算可取代所有加法和乘法吗?
理论上加法与乘法可以递归拆成位级操作,但会极度降低可读性;面试题除外,日常编码勿滥用。


结语
从最简单的奇偶判断,到 Fenwick 树与前缀和,再到加密算法中的 S-box 置换,位运算始终扮演着 CPU 拥抱二极管的初恋角色。掌握它们,你就获得了与「电子产品最底层语言」对话的钥匙。👉 现在就亲自动手测试这些魔法,看看能带来多少性能惊喜。