这段代码用位运算计算整数的绝对值:
int myAbs(int x)
{
const int bit31 = x >> 31;
return (x ^ bit31) - bit31;
}它的核心思路是:先根据符号位生成一个掩码 bit31,再用同一条表达式同时处理正数和负数。
符号掩码
x >> 31 依赖的是 算术右移(arithmetic right shift):右移时,左边空出的位不是随便填 0 或 1,而是用符号位填充,以保持数值的符号不变(右移一位相当于除以 2)。
| 右移类型 | 左边补什么 | 适用 |
|---|---|---|
| 算术右移 | 符号位(正数补 0,负数补 1) | 有符号 int |
| 逻辑右移 | 始终补 0 | 无符号 unsigned |
在常见的 32 位补码整数中,最高位(第 31 位)就是符号位:
x >= 0时,符号位为0;右移 31 次,左边每次补的都是0,最终 32 位全0,结果是0。x < 0时,符号位为1;右移 31 次,左边每次补的都是1,最终 32 位全1。
负数看起来是「补 1」,本质上是符号位本来就是 1,算术右移只是在复制符号位。右移 31 次后全 1,在补码里这个位模式恰好表示 -1:
x = -5 (32 位补码,省略中间位)
...11111111 11111011
>> 1 (左边补符号位 1)
...11111111 11111101 // -3,符号保持不变
>> 31 (符号位 1 不断复制到左边)
11111111 11111111 // 32 位全 1 = -1所以 bit31 只有两种值:
x 的符号 | bit31 |
|---|---|
| 非负数 | 0 |
| 负数 | -1 |
表达式如何工作
最终返回值是:
(x ^ bit31) - bit31当 x 是非负数时:
bit31 = 0x ^ 0仍然是xx - 0仍然是x
当 x 是负数时:
bit31 = -1,二进制全是1x ^ -1等价于按位取反,也就是~x~x - (-1)等价于~x + 1
而在补码表示中,~x + 1 正好是 -x。因此负数会被转换成对应的正数。
示例
以 x = -5 为例,使用 8 位二进制方便观察:
x = 11111011 // -5 的补码
bit31 = 11111111 // -1,全 1 掩码
x ^ bit31 = 00000100 // 按位取反,得到 4
再减 bit31 = 00000100 - 11111111
= 00000100 + 00000001
= 00000101 // 5所以 myAbs(-5) 返回 5。
需要注意的限制
这段写法适合理解补码和位运算技巧,但在 C 语言里有几个前提:
- 它假设
int是 32 位,所以用x >> 31取符号位。 - 它依赖
int右移为算术右移(左边补符号位);C 标准对负数右移的结果是实现定义行为,但 x86、ARM 等常见平台均符合上述行为。 - 它无法正确表示
INT_MIN的绝对值,因为INT_MIN的正数结果超出了int能表示的范围。
因此,面试或学习时可以用它解释位运算技巧;工程代码里应优先选择更清晰、边界行为更明确的实现。