CC++ & Algorithm

C++原码、反码和补码

困难52
语言版本:C++Python
概述:计算机给数字做“变身术”:正数直接上场,负数要经过原码、反码、补码三步变形才能被计算。

计算机里的“变身术”——原码、反码和补码

你知道吗?计算机内部只认识0和1这两个数字。那如果我们要表示 负数,比如“欠你5块钱”,计算机该怎么写呢?总不能写一个“-”号进去吧?聪明的科学家们发明了一套 “变身术”:先把数字变成二进制,再通过原码、反码、补码三步,让负数也能用0和1表示,而且还能跟正数一起做运算。

下面我们就来一步步揭开这个变身过程。想象一下,你有一张只能写0和1的纸条,要想写负数,就得用这些特殊的“密码”。


1. 原码——最简单的“带符号画法”

原码 是最直白的方法:

  • 先写出这个数的绝对值(就是去掉负号)的二进制。
  • 然后在最前面(最高位)加一个 符号位:0 表示正数,1 表示负数。

比如,我们用8位二进制(8个格子)来写数字:

数字绝对值二进制加上符号位(原码)
+5000010100000101 → 00000101
-5000010110000101 → 10000101

正数的原码和它本身一样,负数的原码就是把符号位改成1。

生活例子:就像在记零花钱时,你在数字前画个“+”或“-”号。原码就是那个有符号的黑板报。

问题来了:如果直接用原码做加法,会出错。比如你想算 (+5) + (-5),希望结果是0。但把原码直接相加:

  00000101   (+5)
+ 10000101   (-5)
-----------
  10001010   (结果变成了 -10? 不对!)

这就好比把“欠5块”和“有5块”一起算,结果变成了“欠10块”,完全不对。所以科学家们得继续改进。


2. 反码——负数的“镜像整容”

为了修补原码的漏洞,反码 诞生了。规则很简单:

  • 正数的反码 = 原码(不变)。
  • 负数的反码:符号位保持1不动,其他所有位 取反(1变0,0变1)。

拿 -5 举例:

  • 原码:10000101
  • 符号位是 1,其他位 0000101 取反 → 1111010
  • 组合起来:1 1111010 → 11111010
数字原码反码
+50000010100000101(不变)
-51000010111111010

生活例子:就像你在黑板上写一个数字,然后拿一张镜子,把非符号的部分变成相反的影子。比如“+5”的影子还是“+5”,但“-5”的影子会变成一大堆1(除了最前面那个负号)。

试试用反码做加法:(+5) + (-5)

  00000101   (+5的反码)
+ 11111010   (-5的反码)
-----------
 11111111   (结果全是1)

结果是11111111,这个数如果当作反码,它对应的原码是多少?
反码11111111 → 符号位1,其他位取反得0000000,原码就是10000000,代表 -0。
但 -0 和 +0 是两个不同的数,这在数学上很奇怪。虽然比原码好了一些,但还不够完美。


3. 补码——让计算“自动归零”的完美方案

补码 在反码的基础上 加1,它是计算机最终采用的方案。

  • 正数的补码 = 原码(不变)。
  • 负数的补码:先求反码,然后加1。

继续用 -5 演示:

  • 原码:10000101
  • 反码:11111010
  • 补码:11111010 + 1 = 11111011
数字原码反码补码
+5000001010000010100000101
-5100001011111101011111011

现在用补码做加法 (+5) + (-5)

  00000101   (+5的补码)
+ 11111011   (-5的补码)
-----------
 100000000   (注意:最高位多出的1被丢弃,因为只有8位)
   00000000   (结果正好是0!)

太棒了!丢弃进位后,结果是00000000,正好是0。而且减法也能通过补码变成加法,比如 7 - 3 可以写成 7 + (-3),计算机只需要一个加法器就够了,CPU设计变得非常简单。

生活例子:就像钟表上的时间,12点之后是1点,其实是“加12”再回环。补码就是让负数变成一个大正数,使得加法自动产生正确的循环结果。比如在12小时制里,-3小时等价于+9小时,因为 12 - 3 = 9。补码就是利用这种“模”的思想。


4. 常见错误(新手容易踩的坑)

  • 把负数的原码当成计算机内部存储:很多同学以为计算机存 -5 就是 10000101,其实存的是 11111011(补码)。一定要记住:负数在内存里是补码形式

  • 混淆位数:原码、反码、补码的位数必须一致。比如刚才的例子是8位,如果换成16位,-5的原码就变成 1000000000000101,反码 1111111111111010,补码 1111111111111011。位数不同,结果也不同。

  • 忘记了正数的补码就是原码:正数根本不需要变身,所以它的原码、反码、补码都是同一个二进制数。

  • 手动计算负数补码时忘记加1:反码加1是补码的关键步骤,漏掉就会得到反码,结果会差1。


5. 完整示例——用C++看负数的“真面目”

下面这段代码可以输出某个整数在内存中的补码形式,也就是它真正的二进制样子。

#include <iostream>
#include <bitset>   // 用来方便地输出二进制
using namespace std;

int main() {
    // 定义两个整数:一个正数,一个负数
    int positive = 5;       // 正整数
    int negative = -5;      // 负整数

    // 把int强制转换成unsigned int(无符号整数),
    // 这样bitset就能把内存中的补码直接按二进制显示出来
    unsigned int uPos = (unsigned int)positive;   // 正数转换成无符号
    unsigned int uNeg = (unsigned int)negative;   // 负数转换成无符号

    // 输出二进制表示,bitset<8>表示只显示8位
    cout << "5的二进制:" << bitset<8>(uPos) << endl;
    cout << "-5的二进制:" << bitset<8>(uNeg) << endl;

    // 再试试其他数字
    int x = 0;                 // 零
    int y = -1;                // -1的补码全是1
    unsigned int ux = (unsigned int)x;
    unsigned int uy = (unsigned int)y;
    cout << "0的二进制:" << bitset<8>(ux) << endl;
    cout << "-1的二进制:" << bitset<8>(uy) << endl;

    return 0;
}

运行结果:

5的二进制:00000101
-5的二进制:11111011
0的二进制:00000000
-1的二进制:11111111

看到没?-5变成了11111011,-1变成了11111111(全1)。这就是计算机里负数的真正模样。


6. 相关知识点指引

  • 有符号整数与无符号整数:C++中 int 默认是有符号的,而 unsigned int 没有符号位,所有位都用来表示数值。利用这一点,我们才能看到负数的补码。

  • 位运算:补码的基础是取反(~)和加1。你可以在C++里用 ~x + 1 来快速得到一个数的相反数(前提是x是整数)。这就是补码的妙用。

  • 溢出与循环:补码的加法会产生进位丢弃,所以会出现“溢出”现象。比如 127 + 1 在8位有符号整数中会变成 -128,这是因为补码的循环性质。这一点在游戏中经常用到,比如循环计数器。

  • 为什么选择补码:因为补码可以让加法和减法统一,节省硬件成本,而且只有一个0(不像原码和反码有+0和-0)。所有现代计算机都用补码表示有符号整数。

你现在已经掌握了计算机存储负数的秘密武器。下次看到 -5 的二进制,别忘了它是经过原码→反码→补码三步变身后的结果!

例题精讲

1单选题

在8位二进制表示下,整数-5的补码是?

A10000101
B11111011
C11111010
D00000101
2单选题

计算机内部存储有符号整数时,通常采用哪种编码?

A原码
B反码
C补码
D移码
3判断题

正数的原码、反码和补码完全相同。

4填空题
以下代码使用bitset输出-3的8位补码表示,请填空:
int x = -3;
std::bitset<8> b(___);
std::cout << b;
5单选题

已知一个8位补码为11110110,对应的十进制整数是?

A-10
B-122
C122
D10