答案是:n > 0 && (n & (n - 1)) == 0。因n=0时0 & (0-1)平台相关,负数减一触发未定义行为,且2的幂仅定义于正整数;即使unsigned类型,0u-1u回绕仍致误判,故必须前置n > 0(或n != 0)并依赖短路求值。

为什么 n & (n - 1) == 0 不能直接用
这个表达式本身不带安全边界,0 和负数都会让它“意外为真”或行为未定义。比如:0 & (0 - 1) 在补码下是 0 & 0xFFFFFFFF(32 位),结果非零但具体值依赖平台;而 INT_MIN 减 1 会溢出,触发未定义行为。数学上 2 的幂只定义在正整数范围(2^k, k ≥ 0),所以必须显式排除非正输入。
int 类型下最稳妥的一行写法
直接返回布尔表达式即可,无需分支:
return n > 0 && (n & (n - 1)) == 0;
关键点:
-
n > 0必须放在前面,利用短路求值避免对 n - 1 -
(n & (n - 1))必须加括号,否则&优先级低于-,实际算成n & n - 1,等价于(n & n) - 1,完全错误 - 对
int安全:所有合法 2 的幂(1、2、4…)都满足该条件,且n == 1时1 & 0 == 0成立
换成 unsigned int 就能省掉 n > 0 吗
不能。虽然 unsigned 规避了负数溢出问题,但 0U - 1U 会回绕成 UINT_MAX,导致 0U & UINT_MAX == 0,仍误判 0 为真。所以即使类型是无符号,也必须保留 n != 0 或 n > 0 判断。更清晰的写法是:
return n != 0 && (n & (n - 1)) == 0;
这比 n > 0 更贴近无符号语义,且编译器优化效果一致。
性能和可移植性容易被忽略的细节
这个判断确实是 O(1),但跨平台时要注意:
- 在
int上,若传入INT_MIN(如 -2147483648),n - 1是未定义行为,哪怕条件被短路——某些激进优化可能提前计算子表达式,建议输入前做范围校验或转为unsigned处理 - 模板泛型代码中,如果参数是
T,不能假设T支持&和减法,需加std::is_integral_v<t></t>约束 - Clang/GCC 对该模式识别良好,通常内联为单条
lea+test指令,但 MSVC 在 /O1 下偶尔保留分支,建议加[[likely]](C++20)提示
真正难的不是写出那行代码,而是确保它在边界输入、不同整数类型、不同编译器和优化级别下,始终只对 1、2、4、8… 返回 true。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











