__builtin_popcount最快,因其直接映射cpu的popcnt指令、单周期完成,但仅gcc/clang支持且必须传入无符号类型,负数需先转为unsigned int,long long则须用__builtin_popcountll。

用 __builtin_popcount 最快,但只在 GCC/Clang 有效
对无符号整数或非负 int,GCC 和 Clang 提供内建函数 __builtin_popcount(对应 32 位)和 __builtin_popcountll(对应 64 位),直接编译成 CPU 的 popcnt 指令,1 个周期完成。
常见错误:传入负数时行为未定义(因为底层按补码解释,但 __builtin_popcount 不做符号扩展处理);传 long long 却用 __builtin_popcount(少个 ll)导致高位被截断。
- 确保输入是
unsigned int或非负int;负数先转成无符号再调用 -
long long必须用__builtin_popcountll - MSVC 不支持,跨平台项目需加宏判断:
#ifdef __GNUC__
用 std::bitset::count() 简单可靠,适合教学或小数据
std::bitset 把整数转成固定长度位序列再统计,语义清晰、可读性强,且标准库保证跨编译器一致。
性能代价明显:构造 bitset 涉及复制和静态长度推导,对单个 int 来说开销远大于内置函数;但若已有一个 bitset 对象反复查询,count() 是 O(1)。
- 写法:
std::bitset(x).count()(注意模板参数必须是编译期常量) - 对
long long用std::bitset(x).count() - 不能用于运行时决定位宽的场景(比如想传
n动态指定长度)
手写位运算(Brian Kernighan 算法)兼容性最强、逻辑可控
核心是 n & (n - 1) 每次清除最低位的 1,循环次数等于 1 的个数。不依赖编译器,所有整型都适用,包括负数(按补码计数)。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
比朴素移位检查快——最坏情况(全 1)下也只循环 1 的个数次,而非固定 32/64 次;但分支预测失败时可能略慢于 popcnt 指令。
- 示例:
int popcount(int n) { int c = 0; while (n) { n &= n - 1; c++; } return c; } - 注意:对
unsigned更安全,避免负数右移未定义行为(虽然这里没用右移) - 如果输入可能是 0,循环体不会执行,返回 0 —— 正确
别用 std::popcount(C++20)除非确定环境支持
C++20 引入了 <bit></bit> 头里的 std::popcount,语义同 __builtin_popcount 但标准化了。问题在于:MSVC 2019 v16.10+ 才开始支持,GCC 10+、Clang 11+ 支持,但很多嵌入式或旧构建环境仍无。
若项目要求 C++20 且能控制工具链,它是最推荐的现代写法;否则容易因缺少头文件或未开启 -std=c++20 导致编译失败。
- 必须包含
<bit></bit>,且参数类型必须是无符号整型(unsigned int、unsigned long long等) - 传
int会编译失败(类型不匹配),需显式转换:std::popcount(static_cast<unsigned>(x))</unsigned> - Clang 默认不启用 C++20,需加
-std=c++20;GCC 同理
实际选哪个,取决于你是否能控制编译器、是否需要负数支持、以及是否愿意为跨平台牺牲一点性能。最常踩的坑是混用类型和忽略编译器差异——比如在 MSVC 里硬写 __builtin_popcount,或者给 std::popcount 传带符号整数。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










