__builtin_popcount最快,它直接映射到CPU的POPCNT指令,单周期完成;但仅GCC/Clang支持,MSVC不兼容,且必须传入无符号类型,否则负数会因补码解释导致结果错误。

用 __builtin_popcount 最快,但只在 GCC/Clang 有效
GCC 和 Clang 提供了内置函数 __builtin_popcount(对 unsigned int)和 __builtin_popcountll(对 unsigned long long),底层直接映射到 CPU 的 POPCNT 指令,单周期完成,性能碾压手动循环。但注意:MSVC 不支持,跨平台项目里硬写会编译失败。
常见误用是传入有符号数——比如 int x = -1;,传给 __builtin_popcount(x) 会把负数按补码解释为很大的正整数,结果不是你想要的“-1 的二进制表示里有几个 1”。务必先转成无符号类型:
int x = -1; std::cout << __builtin_popcount(static_cast<unsigned int>(x)); // 正确:输出 32(在 32 位 int 下)
- 仅限 GCC/Clang;MSVC 请改用
std::popcount(C++20)或手动实现 - 参数必须是无符号整型,否则行为未定义
- 对
char或short传参会自动提升,但建议显式转换避免歧义
C++20 的 std::popcount 是标准且安全的选择
std::popcount 在 <bit> 头文件中,要求参数为无符号整型(unsigned char、unsigned int 等),编译器会在支持时自动用硬件指令,不支持时回退为高效查表或位运算,语义清晰、可移植性强。
它拒绝有符号类型,编译期就报错,比 __builtin_popcount 更安全:
立即学习“C++免费学习笔记(深入)”;
#include <bit> unsigned int x = 0b1011; std::cout << std::popcount(x); // 输出 3
- 必须包含
<bit>,且编译器需开启 C++20 支持(如-std=c++20) - 不接受
int、long等有符号类型,连隐式转换都不行 - 对
unsigned long long同样有效,无需额外后缀
手动实现:n & (n-1) 清零最低位 1,适合教学或嵌入式环境
这个技巧利用了位运算特性:对任意非零整数 n,n & (n-1) 会把 n 的二进制表示中最右边的 1 变成 0。循环执行直到 n == 0,循环次数就是 1 的个数。时间复杂度是 O(1)(最多 32 或 64 次),比逐位检查快一倍。
int count_bits(unsigned int n) {
int cnt = 0;
while (n) {
n &= n - 1;
++cnt;
}
return cnt;
}- 输入必须是无符号类型,否则
n-1对负数可能溢出或逻辑异常 - 相比查表法,空间占用为 O(1),适合内存受限场景
- 比
n >>= 1; cnt += n & 1;少做很多次移位和与操作,尤其当 1 很少时优势明显
为什么不能直接用 std::bitset<32>(n).count()?
虽然 std::bitset::count() 语义直观,且标准库保证高效实现,但它有隐藏开销:构造 std::bitset 需要复制和初始化整个位容器,对高频调用(如图像处理像素遍历)可能成为瓶颈。而且模板大小必须在编译期确定,无法适配运行时才知道位宽的场景(比如处理 uint64_t 但只关心低 24 位)。
- 每次调用都触发对象构造/析构,哪怕优化后也可能残留少量指令
- 位宽写死(如
bitset<32>),对long long需手动改成bitset<64>,易出错 - 在 constexpr 上下文中,
std::popcount支持编译期计算,bitset::count则不一定(取决于实现)
C++ 统计 1 的个数没有唯一“最佳”方案,关键看你的编译器、标准版本和性能敏感度。最易踩的坑是忽略有符号/无符号类型差异,以及在跨平台项目中盲目依赖编译器内置函数。


















