原始题目

以下代码的输出是什么?

int x = -5;
x = x >> 1;
cout << x;
  • A. -2
  • B. -3
  • C. -5
  • D. 2

答案:B(-3)


为什么是 -3,不是 -2

x >> 1 对负数是算术右移:右移时高位补符号位 1(而不是补 0),等价于向下取整的除以 2

-5 / 2 = -2.5   →   向下取整(floor)= -3

所以 x >> 1 = floor(-5 ÷ 2) = -3

补码位级演示

-5  = 11111011   (8位补码示意)
>>1 = 11111101   ← 最高位补符号位 1,低位 1 被挤掉
    = -3

32 位同理:

-5  = ...11111011
>>1 = ...11111101  =  -3

最大陷阱:右移 ≠ 整数除法

很多人用除法直觉算出 -2-5 / 2 = -2),于是选 A——错了

表达式 结果 取整方向
-5 >> 1 -3 向下(floor,往 -∞)
-5 / 2 -2 向零(trunc,往 0)
5 >> 1 2 5/2,两者一致
6 >> 1 3 6/2,两者一致
-6 >> 1 -3 -6/2,偶数无差别

结论:只有负数且为奇数时,>> 1/ 2 才差 1。

通用规律

\[x \gg n = \left\lfloor \frac{x}{2^n} \right\rfloor\]

对正数、负数、零都成立。

7 >> 1   // 3   floor(3.5)=3
-7 >> 1  // -4  floor(-3.5)=-4
-7 / 2   // -3  trunc(-3.5)=-3
-8 >> 2  // -2  floor(-8/4)=-2

关于“实现定义”

  • C++20 之前:标准把有符号数右移留作实现定义,但所有主流编译器(GCC / Clang / MSVC)都实现为算术右移(补符号位)。
  • C++20 起:标准明确规定有符号右移就是算术右移x >> n\(x / 2^n\) 向下取整。

竞赛笔试中一律按算术右移处理,直接套用 floor(x / 2ⁿ)

考点总结(CSP-J 初赛选择题)

  1. 右移 n 位 ≈ 除以 \(2^n\),但对负奇数是向下取整,比 / 少 1。
  2. 正数、偶数:右移与除法结果相同,可以放心用 >> 1 代替 / 2(还能加速)。
  3. 算术右移补符号位,逻辑右移补 0(无符号数 unsigned 才是逻辑右移)。
  4. 秒杀技巧:题干出现负数右移,先算 x / 2,若 x 是负奇数再把结果减 1

思考题

unsigned int u = -5;(即 \(2^{32}-5\)),u >> 1 的结果是多少?

答案 2147483645。因为 unsigned逻辑右移,高位补 0:\((2^{32}-5) / 2 = 2147483645.5\) 向下取整 = 2147483645。和 int-3 是完全不同的世界。