位运算仅在特定场景(如状态压缩、寄存器控制、算法剪枝)真正高效;&替代%须满足除数为2的幂且被除数非负,否则结果错误,且现代编译器已自动优化,手动替换反而降低可读性。

位运算不是“拿来就快”的银弹,它只在特定场景下真正起效:状态压缩、硬件寄存器控制、算法剪枝(如N皇后)、比特级统计等。盲目用 & 替 % 或 ^ 换变量,大概率白忙还埋坑。
什么时候该用 & 替代 % 和 /
仅当除数是 2 的幂(如 2、4、8、16)且被除数非负时,x & (n-1) 才等价于 x % n,x >> k 才等价于 x / (1 。
- 正确示例:
addr & 0x7等价于addr % 8(前提是addr >= 0) - 错误示例:
val & 0x5≠val % 6——0x5不是n-1形式,结果完全错乱 - 负数陷阱:
-1 >> 2在 x86 是 -1(算术右移),而-1 / 4是 0(向零取整),行为不一致 - 编译器早做了这事:现代 GCC/Clang 对
x % 8会自动转成x & 7,手动改反而干扰优化、降低可读性
怎么安全地操作标志位(&、|、~、^)
标志位管理是位运算最稳妥、最高频的用途,但必须严格区分语义:
- 检测某位是否置位:
if (flags & FLAG_READ)—— 正确;if (flags & FLAG_READ) == FLAG_READ多余;if (flags ^ FLAG_READ)是错的(判断“不同”,不是“包含”) - 开启某位:
flags |= (1U —— 用 <code>U后缀防int溢出,别写1 (在 16 位系统上可能溢出) - 关闭某位:
flags &= ~(1U —— <code>~是按位取反,不是逻辑非;!FLAG_DEBUG是 0,全清零 - 翻转某位:
flags ^= (1U —— 可逆,适合 toggle 场景,比如调试开关 - 优先级坑:
flags & 0x04 == 0x04实际执行的是flags & (0x04 == 0x04)→flags & 1,必须加括号:(flags & 0x04) == 0x04
为什么 __builtin_popcount 比手写循环快得多
它直接映射到 CPU 的 POPCNT 指令(x86-64),一条指令完成 64 位计数;手写循环要至少 64 次分支+移位,流水线全卡住。
立即学习“C++免费学习笔记(深入)”;
- 正确用法:
int ones = __builtin_popcount(static_cast<uint32_t>(v))</uint32_t>—— 输入必须是无符号类型,传负数是未定义行为 - 跨平台注意:MSVC 用
_mm_popcnt_u32(需#include <nmmintrin.h></nmmintrin.h>并确认 CPU 支持);老平台 fallback 查表法比循环快 - 别用
std::bitset::count()在热循环里高频调用——底层可能没内联,或生成额外跳转
N皇后问题里位运算到底优化了什么
它把 O(N) 的冲突检查压缩成一次 row | ld | rd 和一次 & ~,把“哪些列能放”直接算成一个掩码 pos,再用 pos & -pos 提取最低位安全列——整个过程全是位指令,无分支、无循环、缓存友好。
- 关键变量:
row(已占列)、ld(左对角线限制)、rd(右对角线限制),全用uint32_t表示,避免符号扩展歧义 - 核心剪枝:
pos = upperlim & ~(row | ld | rd)—— 三行状态合并,一气呵成 - 不要自己实现
lowbit:用pos & -pos,比循环找最低 1 位快一个数量级 - 这不是语法糖:当 N=15 时,递归调用从约 2.3 亿次降到 500 万次以内,提速主因是消除了每次放置前的 O(N) 扫描
真正难的不是写出位运算表达式,而是把问题建模成“哪些 bit 能表示哪些约束”。一旦建模错,后面再快也没用——比如用有符号类型做移位、忽略负数语义、混淆 & 和 ^ 的用途,都会导致结果错误且难以调试。


















