最直观选择是std::bitset,它将整数转为位序列后调用count(),内部通常编译为单条popcnt指令(x86-64启用-mpopcnt时),语义清晰且不易出错。

用 std::bitset 统计数组每个元素的置位数最直观
对固定宽度整数(如 uint32_t),std::bitset 是最易读、不易出错的选择。它把整数转为位序列后调用 count(),内部通常编译为单条 popcnt 指令(x86-64 启用 -mpopcnt 时)。
实操建议:
- 确保包含
<bitset></bitset>和<cstdint></cstdint> - 数组元素类型必须能隐式转为无符号整型;有符号负数会按补码解释,结果仍正确但语义需自洽
- 若数组是
int且含负值,明确用static_cast<uint32_t>(x)</uint32_t>避免实现定义行为
uint32_t arr[] = {0b1010, 0b1100};
int total = 0;
for (auto x : arr) {
total += std::bitset(x).count();
}
// total == 6
手写 popcount 循环适用于嵌入式或需控制精度的场景
当不能用 STL(如裸机环境),或需处理非标准位宽(如 24 位传感器数据),手动移位统计更可控。注意:别用 x & 1 然后 x >>= 1 —— 对有符号负数右移是实现定义行为。
实操建议:
- 统一转为无符号类型再操作,例如
auto u = static_cast<unsigned int>(x)</unsigned> - 用
u &= u - 1清最低位 1(Brian Kernighan 算法),比逐位检查快,尤其稀疏时 - 避免对
char数组直接循环——每个char只有 1 位,但你要的是“每个字节里 1 的个数”,不是“每个元素是否非零”
int popcount(unsigned int x) {
int c = 0;
while (x) {
x &= x - 1;
c++;
}
return c;
}
用 __builtin_popcount 获得最佳性能(GCC/Clang)
这是编译器内建函数,直接映射到 CPU 的 popcnt 指令(Intel/AMD)或等效指令(ARMv8.2+)。比 std::bitset 更轻量,无模板实例化开销,且支持 unsigned long 和 unsigned long long 变体。
实操建议:
- 只传入无符号整数;传
int可能触发符号扩展误算(如__builtin_popcount(-1)在 32 位系统上返回 32,因 -1 被提升为unsigned int全 1) - 跨平台时加宏判断:
#ifdef __GNUC__,否则回退到手写循环 - 对数组,避免在循环内反复调用——现代编译器通常能自动向量化多个
__builtin_popcount,但确保数组对齐有助于生成更优代码
处理 char 或 uint8_t 数组时别漏掉类型提升陷阱
常见错误是写 for (auto b : bytes) total += __builtin_popcount(b);——此时 b 是 int(因为 char 提升),高位全 0,结果没错;但若 bytes 是 signed char 且含负值(如 0xFF),提升后变成 0xFFFFFFFF(32 位),__builtin_popcount 返回 32 而非预期的 8。
正确做法:
- 声明数组为
uint8_t或unsigned char - 或显式截断:
__builtin_popcount(static_cast<unsigned int>(static_cast<unsigned char>(b)))</unsigned></unsigned> - 若用
std::bitset(b).count(),std::bitset构造函数接受unsigned long long,会自动截断低位,更安全
C++ 标准没规定内置 popcount,所以“怎么算”最终取决于你愿不愿意依赖编译器扩展、能否用 STL、以及数据来源是否带符号。最易踩的坑不是算法,而是类型提升和符号扩展——尤其是从文件或网络读进来的原始字节,常被误声明为 char。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











