位运算符 &、|、^ 分别用于检测、置位、翻转特定位;常见错误是忽略运算符优先级导致误判,如 flags & 0x04 == 0x04 应写为 (flags & 0x04) == 0x04;置第 n 位用 flags |= (1U << n)。
怎么用
做开关位和翻转位
位运算不是炫技,是直接改内存里某个 bit 的唯一办法。比如控制硬件寄存器、压缩标志位、实现布隆过滤器——
用来“查”某一位是否为 1,
用来“开”某一位,
用来“翻”某一位。
常见错误:写成
,结果因为优先级问题变成
是对的,但很多人漏括号,实际执行的是
→
,全错。
开第 n 位(从 0 开始):
后缀防符号扩展
关第 n 位:
是按位取反,不是逻辑非
翻第 n 位:
查第 n 位:
什么时候该用,什么时候别碰
适合编译期确定大小、需要可读性高、且不频繁修改的场景,比如状态机标志、协议字段解析。但它不是万能替代品:底层是静态数组,不能动态 resize;拷贝开销比裸
大;部分老编译器(如 GCC 4.8)对
生成的代码不够优化。
用它:定义固定 16 个标志位,要支持
、
这种语义清晰的操作
别用它:循环里高频读写单个 bit(不如移位+掩码快),或位数超 128(栈上占空间大,
就 128 字节)
注意:
在位数 >
时抛
,
同理
为什么
比手写循环快十倍
因为它是编译器内建函数,直接翻译成 CPU 的
指令(Intel/AMD x86-64 支持),一个指令周期搞定 64 位计数。手写 while(n) { count += n&1; n>>=1; } 要至少 64 次分支+移位,现代 CPU 流水线全卡住。
立即学习
“
C++免费学习笔记(深入)
”;
GCC/Clang 专属:
(
)、
(
)
MSVC 用
/
,需包含
,且要确认 CPU 支持(可用
查)
跨平台封装建议:宏判断编译器,fallback 到查表法(256 字节表 + 四次查表)比循环快得多
注意:输入为 0 时返回 0,没问题;但传负数给
是未定义行为(它只认无符号语义)
左移右移陷阱:为什么
上是未定义行为
C++ 标准规定:对有符号整数做移位,若结果无法用该类型表示,就是未定义行为(UB)。
通常是 32 位补码,最大正数是 2
31
−1,
永远用无符号类型做移位:
右移有符号数是算术右移(符号位填充),但标准只要求“实现定义”,不同平台可能不同;无符号右移
一定是逻辑右移(高位补 0)
移位位数不能 ≥ 类型宽度,
是 UB,哪怕你用的是 64 位系统
Clang/GCC 加
能捕获这类问题,开发期务必开
位运算真正难的不是写法,是记住哪些操作在什么条件下会垮掉——尤其是跨平台、跨编译器、开 O2 之后。别信“我本地跑得通”,得看标准怎么写的,再看生成的汇编是不是真用了
或
。
&|^&|^flags & 0x04 == 0x04(flags & 0x04) == 0x04flags & (0x04 == 0x04)flags & 1flags |= (1U —— 一定要用 Uflags &= ~(1U —— ~!flags ^= (1U (flags & (1U —— 别直接用作 bool,某些编译器会警告std::bitsetstd::bitsetuint32_tbitset::operator[].set(3).test(7)bitset<1024>bitset::to_ulong()sizeof(unsigned long)*8std::overflow_errorto_ullong()__builtin_popcountPOPCNT__builtin_popcount(x)unsigned int__builtin_popcountll(x)unsigned long long__popcnt__popcnt64__cpuid__builtin_popcount1 在 32 位 intint1 等于 231,溢出,编译器可以生成任意代码——包括看似正常但实际在某些优化等级下崩溃。1U 、1ULL >>1U 对 uint32_t-fsanitize=undefinedPOPCNTBTS