跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

C++怎么实现各种位操作_C++高效位运算汇总【技巧】

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

相关文章