C++原码、反码和补码
困难52计算机里的“变身术”——原码、反码和补码
你知道吗?计算机内部只认识0和1这两个数字。那如果我们要表示 负数,比如“欠你5块钱”,计算机该怎么写呢?总不能写一个“-”号进去吧?聪明的科学家们发明了一套 “变身术”:先把数字变成二进制,再通过原码、反码、补码三步,让负数也能用0和1表示,而且还能跟正数一起做运算。
下面我们就来一步步揭开这个变身过程。想象一下,你有一张只能写0和1的纸条,要想写负数,就得用这些特殊的“密码”。
1. 原码——最简单的“带符号画法”
原码 是最直白的方法:
- 先写出这个数的绝对值(就是去掉负号)的二进制。
- 然后在最前面(最高位)加一个 符号位:0 表示正数,1 表示负数。
比如,我们用8位二进制(8个格子)来写数字:
| 数字 | 绝对值二进制 | 加上符号位(原码) |
|---|---|---|
| +5 | 0000101 | 00000101 → 00000101 |
| -5 | 0000101 | 10000101 → 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
| 数字 | 原码 | 反码 |
|---|---|---|
| +5 | 00000101 | 00000101(不变) |
| -5 | 10000101 | 11111010 |
生活例子:就像你在黑板上写一个数字,然后拿一张镜子,把非符号的部分变成相反的影子。比如“+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
| 数字 | 原码 | 反码 | 补码 |
|---|---|---|---|
| +5 | 00000101 | 00000101 | 00000101 |
| -5 | 10000101 | 11111010 | 11111011 |
现在用补码做加法 (+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 的二进制,别忘了它是经过原码→反码→补码三步变身后的结果!
例题精讲
在8位二进制表示下,整数-5的补码是?
计算机内部存储有符号整数时,通常采用哪种编码?
正数的原码、反码和补码完全相同。
以下代码使用bitset输出-3的8位补码表示,请填空:
int x = -3;
std::bitset<8> b(___);
std::cout << b;已知一个8位补码为11110110,对应的十进制整数是?