CC++ & Algorithm

为什么 `-5` 在内存里长着一张“陌生脸”?——聊聊原码、反码和补码

你有没有试过,在 C++ 里打印一个负数的二进制,结果发现它跟你想象中完全不一样?比如 -5,你以为是 10000101,打印出来却是 11111011。这中间到底发生了什么?别急着怀疑编译器,这其实是计算机在对你使眼色——它想告诉你:负数,从来不是“符号位 + 绝对值”那么简单。

从“欠钱”说起:原码的直觉陷阱

如果我们真的只用原码表示负数,那 -5 就是 10000101:最高位的 1 表示负号,后面 5 的二进制。看起来挺直观的,对吧?就像记账本上画个“-”号,谁都懂。

但问题出在运算上。你用原码试试 (+5) + (-5)

  00000101
+ 10000101
-----------
  10001010

结果是 -10?这等于说“你有 5 块,欠 5 块,最后算成欠 10 块”——显然不对。

原码能“表示”负数,但用它来“计算”负数,会闹笑话。于是科学家走上了改造之路。

反码:解决了部分问题,但留下了“-0”

反码的定义很简单:正数不变,负数保持符号位为 1,其他位取反。-5 的反码就是 11111010

这时候再算 (+5) + (-5)

  00000101
+ 11111010
-----------
  11111111

得到 11111111。如果把它当反码还原,它是 10000000,也就是 -0。虽然加法结果不再离谱了,但“-0”和“+0”同时存在,数学上总觉得智商被侮辱了。而且 -0+0 是两个不同的二进制数,白白浪费了一个编码。

补码:把“减法”变成“加法”的那一步

补码的诞生,简直像给整数运算装上了自动导航。规则一句话:反码再加 1

-5

  • 原码:10000101
  • 反码:11111010
  • 补码:11111011

现在重点来了,用补码算 (+5) + (-5)

  00000101
+ 11111011
-----------
 100000000

最高位多出的 1 在 8 位存储时直接被丢弃,留下的结果是 00000000——干净利落的 0。程序员梦寐以求的事出现了:同一个加法器,既能算加法,也能算减法。因为 7 - 3 可以写成 7 + (-3),计算机底层不需要独立的减法电路,CPU 因此省了一大笔成本。

这背后的哲学,其实是“模”的思想。就像钟表上 12 小时制:-3 小时等价于 +9 小时,因为 12 - 3 = 9。补码就是把负数映射成一个大正数,让加法的自然溢出帮你“归零”。

这里可以做一个验证。有次别人问我一道题:“在 8 位二进制表示下,整数 -5 的补码是?” 选项分别是 10000101111110111111101000000101

如果你已经理解了上面的流程,一眼就能看出:

  • 10000101 是原码,符号位 1,后面是 5 的二进制。
  • 11111010 是反码,只做了取反,没加 1。
  • 00000101 是 +5,根本没变过身。

正确答案 11111011,正好是反码加 1。这道题的本质就是考你有没有记住“取反加一”的最后一步,以及是否真的理解负数在内存里永远以补码存在——而不是“符号位 + 绝对值”的原码。

想亲眼看看?几行 C++ 就能揭穿一切:

int negative = -5;
unsigned int uNeg = (unsigned int)negative;
cout << bitset<8>(uNeg) << endl;  // 输出 11111011

int 强制转成 unsigned int,bitset 就能按无符号数输出它的每一位。运行结果就是 11111011。再试试 -1,输出 11111111——全 1,因为 -1 的补码就是所有位都是 1。

计算机为什么非它不可?

另一个常见的面试式问题是:“计算机内部存储有符号整数时,通常采用哪种编码?” 有人会犹豫,原码?反码?补码?

答案当然是补码。除了前面说的统一加减法、只保留一个 0 之外,补码还有一个隐藏优势:它给“溢出”提供了自然的循环行为。8 位有符号整数 127 + 1 会变成 -128,这在原码和反码体系里会变得混乱不堪,但在补码里,它只是一个正常的进位溢出,完全符合模 256 的循环逻辑。

这也是为什么你在写游戏循环计数器、处理 CRC 校验、或者分析一段二进制数据时,看到负数总是“长得不顺眼”——因为你在看它的补码。

最后说两句

原码、反码、补码,本质上是在回答同一个问题:怎么用 0 和 1 表达负数,才能让硬件算得又快又准?

原码是为了给人类看,反码是过渡方案,补码才是机器的心头好。理解了这条演进线,你就不会再把 10000101 当成计算机里的 -5 了。

接下来你可以去研究一下位运算中的 ~+1——你会发现 ~x + 1 恰好等于 -x,这就是补码在 C++ 里的直接体现。再深入一点,看看无符号整数的溢出行为是怎么配合补码的。搞懂这些,你对数据在 CPU 里怎么流转,会有一个全新的认识。


关于作者

我是赵老师,持有 NOI 信息学奥赛教练证书,拥有 15 年以上的软件开发经验,从事信息学少儿编程教学已有 8 年时间。

这些年累计帮助 多名 学生通过编程特长升入自己心仪的目标学校。

如果你在编程学习上有任何疑问,欢迎联系我:18620372957(微信同号)

这篇文章对你有帮助吗?

成为第一个评价的人

评论0

还没有评论,来抢沙发~

评论加载中...

想系统学习这个知识点?查看完整知识点 →