CC++ & Algorithm

C++补码运算与数值范围

困难24
语言版本:C++Python
概述:补码让计算机用一个加法器就能搞定加减法,也给每个整数类型定下了能装多少数字的“箱子大小”。

补码的魔法:一台加法器搞定加减法,每个整数都有它的“箱子”

你有没有想过,计算机只会做加法,那减法、乘法、除法怎么办?其实,计算机用了一种巧妙的“变身术”——补码,把减法偷偷变成了加法。而且每个整数类型就像不同大小的箱子,能装多少数字都是有数的。今天我们就来揭秘补码这两大本领:运算统一数值范围限制

补码让减法变成加法:零花钱加减法

回想一下,我们做加法时直接加就行,但做减法有时要借位,很麻烦。计算机跟人一样,也想做简单的事。补码就是让计算机只用加法电路就能完成减法。怎么做呢?减去一个数,等于加上这个数的负数。例如 5 - 3,计算机算的是 5 + (-3)。那么 -3 在计算机里长什么样?我们来变个魔术:

  1. 先写出 3 的原码(假设用 8 位):00000011
  2. 取反:变成 11111100
  3. 加 1:得到 11111101

这小魔术就是“补码”变身。现在让 5(00000101)加上 11111101

  00000101
+ 11111101
---------
 100000010  (只取最后8位,进位丢掉)

结果是 00000010,也就是 2!没错,5 - 3 = 2,完全正确。

生活例子:假设你零花钱有50块,要花掉20块,你其实可以这样做:先借朋友20块,然后还他20块等于没借……不对,这样想:你只需要在数字上做 50 + (-20),而 -20 的补码形式就是它“借位”后的样子。计算机就是这样玩的。

补码把减法变成加负数,于是计算机只需要一个加法器,电路设计简单又高效。

数值范围:你的整数“箱子”有多大?

每种整数类型就像一个固定大小的“箱子”。比如 char(字符型)通常有 8 个位(bit),每个位只能放 0 或 1,一共 8 个小格子。那么它能表示多少个不同的数字呢?8 个位,每个位有 2 种选择,总共 2^8 = 256 种组合。

  • 如果箱子只装正数unsigned char),那么 0 ~ 255 刚好 256 个数字。
  • 如果要装正数也装负数signed char),就需要一半分给正数和0,一半分给负数。

但为什么范围是 -128 ~ 127,而不是 -127 ~ 127?因为补码让负数里多了一个 -128

把 256 种组合排个序:

  • 0 的补码是 00000000
  • 正数从 1(00000001)到 127(01111111
  • 负数从 -1(11111111)到 -128(10000000

你看,10000000 就是 -128。它没有对应的正数(因为正数最高位必须是0),但这样正好用完 256 种组合。所以 signed char 的范围是 -128 ~ 127。

生活例子:想象一个水杯能装 256 毫升水。如果你只喝白开水(正数),可以装 0~255 毫升。如果你既喝热水(正数)又喝冰水(负数),那你得把一半容量分给冰水,一半分给热水,但冰水可以比热水多1毫升(因为0也算热水这边的)。这就是 -128 和 127 的由来。

溢出:当数字超出箱子,它会绕圈滑回来

如果你给一个 char 变量赋一个超出范围的值,会发生“溢出”。比如 char 最大能装 127,你偏要把 128 放进去,结果会变成 -128。因为补码是循环的:从 127 加 1,二进制 01111111 变成 10000000,正好是 -128。就像一个圆形滑梯,滑到尽头又绕回起点。

我们来看一段代码:

#include <iostream>
#include <climits>   // 包含 CHAR_MIN 和 CHAR_MAX
using namespace std;

int main() {
    char c = 127;                // 最大的正数 127
    cout << "c = " << (int)c << endl;  // 输出 127

    c = c + 1;                   // 127+1,溢出变成 -128
    cout << "c+1 = " << (int)c << endl; // 输出 -128

    c = c - 1;                   // -128-1,再次溢出变成 127
    cout << "c-1 = " << (int)c << endl; // 输出 127,转回来了

    // 打印整数类型的范围(来自 <climits>)
    cout << "char的范围:";
    cout << (int)CHAR_MIN << " 到 " << (int)CHAR_MAX << endl;
    // 输出 -128 到 127

    return 0;
}

运行结果:

c = 127
c+1 = -128
c-1 = 127
char的范围:-128 到 127

看到了吗?数字超出范围后,就像在环形跑道上跑,从最高点一下滑到最低点。理解这个特性,写程序时就能避免奇怪的bug。

常见错误与注意事项

错误1:搞错有符号和无符号的范围

新手常以为 char 就是 0255,其实在大部分编译器里,char 默认是有符号的(-128127)。如果你想要无符号,必须写 unsigned char。比如:

unsigned char score = 200;  // 这个没问题,可以装到255
char score2 = 200;          // 警告!200超过了127,会变成负数

错误2:用 int 时溢出导致逻辑错误

int 一般占 4 字节,范围很大(约 -21亿 ~ 21亿),但如果你算工资或乘法时不小心超出,也会溢出。比如:

int salary = 1000000;        // 100万
salary = salary * 1000;      // 想算10亿,但结果可能溢出变成负数(因为最大只有21亿多)

错误3:循环变量越界

char 做循环变量时,一不小心就绕圈:

for (char i = 0; i < 200; i++) {   // i最大127,加1后变成-128,永远小于200,死循环!
    cout << (int)i << " ";
}

完整示例:比较有符号和无符号的溢出

下面这个程序对比 signed charunsigned char 从最大值加1后的行为:

#include <iostream>
using namespace std;

int main() {
    // 有符号 char
    signed char s = 127;            // 最大值
    s = s + 1;                      // 溢出
    cout << "signed char 127+1 = " << (int)s << endl;   // 输出 -128

    // 无符号 char
    unsigned char u = 255;          // 最大值
    u = u + 1;                      // 溢出
    cout << "unsigned char 255+1 = " << (int)u << endl; // 输出 0(绕回起点)

    // 再减 1
    u = u - 1;                      // 0-1,又溢出
    cout << "unsigned char 0-1 = " << (int)u << endl;   // 输出 255(从另一头绕回)

    return 0;
}

运行结果:

signed char 127+1 = -128
unsigned char 255+1 = 0
unsigned char 0-1 = 255

注意:无符号数的溢出也是循环的,但看起来更“直观”:最大值加1变0,0减1变最大值。

相关指引

  • 整数提升:当 charshort 参与运算时,会自动提升为 int,这会影响溢出判断。
  • 位运算:补码是位运算的基础,比如 ~(取反)和 <<(左移)等。
  • 大数处理:如果数字太大,需要用 long long 或者自定义大数类,避免溢出。
  • 数据类型选择:写好程序时要根据数值范围选合适类型,比如存储年龄用 unsigned char 就够了(0~127岁),存储人口数要用 intlong long

补码不仅让计算机运算更快,还帮我们精确地知道了每种数据类型能装多少“货”。下次写代码时,记得想想你的数字有没有超出箱子哦!

例题精讲

1单选题

在C++中,对于一个8位有符号整数(如char类型),其补码表示的范围是多少?

A-128 ~ 127
B-127 ~ 128
C0 ~ 255
D-127 ~ 127
2判断题

在8位补码表示中,-128的二进制补码是10000000。

3填空题
执行以下C++代码,输出结果是___。
char a = 127;
a++;
std::cout << (int)a;
4单选题

补码运算中,计算-5 + 3的结果(以8位补码表示),十进制结果为多少?

A-2
B-8
C2
D8
5判断题

在C++中,int类型通常采用补码表示,当int值为2147483647(即INT_MAX)时,执行x = x + 1后,x的值变为-2147483648(即INT_MIN)。