☰
GESP三级C++考点:原码、反码、补码详解与位运算实战
2026/10/15 6:25:19 网站建设 项目流程

备考GESP三级C++的同学,十有八九会遇到《原码、反码和补码》这一节。它是整数在计算机里存储方式的底层逻辑,也是位运算、类型转换、表示范围这些考点的地基。文章标题里的“语法知识(一)”已经说明这是个系列,那第一篇就把这个地基打牢。你只要把这个东西真正搞明白,后面不管是看二进制输出、算负数移位、还是做位运算题,都会顺很多。这篇文章适合正在刷GESP三级真题的人,也适合刚学C++但总被负数搞懵的新手,我会把原理、计算步骤、C++验证方法和做题技巧都串到一起。

1. 为什么GESP三级绕不开原码、反码和补码

1.1 这组知识点在考试里到底怎么考

GESP三级的大纲里,进制转换和位运算是固定板块,而原码、反码、补码就是所有整数位级操作的基础。很多同学会把十进制转二进制练得很熟,但一遇到负数就卡住,因为负数没法直接搬那套“除二取余”的方法。负整数在计算机里是以补码形式存在的,所以题目经常围绕这几类出:

第一类是概念选择题,比如“下面哪个是-5的8位补码”。第二类是计算题,给出一个补码让你求十进制值,或者反过来给出负数让你写补码。第三类是结合位运算的分析题,比如“~5的结果是多少”“-8 >> 1等于几”,这些如果不清楚补码,只能靠猜答案。

举几个我在刷题时见过的真实考法:判断“0的补码唯一吗”,计算“10001000作为有符号数的十进制值”,还有“a为负数时,a >> 1是向哪边取整”。每一题背后都需要补码知识做支撑,不是背几个公式就能蒙对的。

1.2 不只是背公式:理解计算机为什么选补码

先问一个问题:计算机内部能不能直接存原码?理论上可以,但硬件会非常别扭。假设8位原码,1是00000001,-1是10000001,想算1 + (-1),原码直接相加:

00000001 + 10000001 ----------- 10000010

这个结果对应-2,显然不对。如果要用原码实现减法,CPU必须先判断符号位、比较绝对值大小、再用大数减小数、最后调整符号,这一套逻辑在电路上既复杂又慢,完全没有必要。

反码也试一下:1的反码是00000001,-1的反码是11111110,相加得到11111111,这是-0。虽然比原码接近正确了,但“0”出现了两种编码,依然不方便。

补码是怎么解决的:

00000001 (+1) + 11111111 (-1的补码) ----------- 100000000

结果产生了第9位,在8位机里这个最高位会被丢弃,剩下的就是00000000,正好等于0。这个“溢出后被丢弃”的动作,不是BUG,反而是设计核心。补码的本质是模$2^8$运算,把溢出舍去后,加法和减法就统一成加法了。这里用钟表类比最好懂:表盘上12点就是0点,从11点调回8点,可以倒拨3格,也可以正拨9格,因为(11 + 9) mod 12 = 8。补码做的事情就是把“负数”映射成一个正数,让减法变成一次正常加法。

理解到这个层面,你再看取反加一的公式,就不会觉得它是天降规则了。

2. 三种编码规则一次讲透

2.1 原码:取值直观,运算绕远路

原码的规则很简单:最高位当符号位,0表示正数,1表示负数,其余位存数值的绝对值。8位二进制为例:

  • +5:00000101
  • -5:10000101

优点是看着就知道是多少,符号位一亮,往下一看就是数值大小。缺点除了刚才说的运算麻烦,还有一个经典问题:0有两张脸。00000000是正零,10000000是负零。同一个数出现两种编码,对数据比较和运算判断都是隐患。所以8位原码能表示的范围是-127到+127,而不是-128到+127,因为有一个编码位被负零占了。

原码做题时容易出错的地方是“符号位不参与数值计算”。求-5的原码,不能直接在5的二进制上把最高位置1,比如+5是00000101,符号位变1得到10000101,这样可以;但如果是-0这种边缘值就要小心,反正遇到原码题,先写绝对值二进制,再补符号位,顺序不能乱。

2.2 反码:向补码过渡的中间站

反码的规则也不复杂:正数的反码等于原码;负数的反码是符号位保持1不动,其余每个数值位取反。比如:

  • +5原码00000101,反码还是00000101
  • -5原码10000101,反码是11111010

反码的价值在于它铺了一条通向补码的桥。反正码本身还不能直接参与常规加法运算,但它和原码的互转非常对称:一个负数,先取反变成反码,再取反又能变回原码。

反码也有正零负零问题:00000000是+0,11111111是-0。所以后来设计补码时,把反码的结果统一加1,顺手就把-0清掉了。你不需要单独背反码的考试知识多深,只要把它当作“原码取反,但符号位不变”的中间状态就行。

2.3 补码:取反加一背后的数学逻辑

补码的规则完整写下来是:

  • 正整数的补码 = 原码 = 反码,最高位为0。
  • 负整数的补码 = 其原码符号位不变、数值位取反,再加1。
  • 0的补码有唯一形式:全0。

比如-5:

原码: 10000101 取反: 11111010 (符号位不动) 加1: 11111011

这个11111011就是-5的8位补码。你也可以用“先求5的二进制,取反加1”来算:5是00000101,按位取反得11111010,加1得11111011,一样的结果。因为这两种思路本质相同,只是符号位和数值位处理方式不同而已。

补码最厉害的一点是0只有一个表示。8位补码里,00000000表示0,不存在任何其他编码表示0。这样负数就比正数多一个名额,范围变成-128到+127。-128的补码一眼看过去容易懵:是10000000。它没有对应的8位原码,但补码就这么直接放着,因为-128在补码体系里属于合理延伸。计算方法:128是10000000,按位取反是01111111,加1得到10000000,正好回到自己。这个数比较特殊,考试容易出。

把三种编码放在一起对比,记忆效率会高很多:

数值原码反码补码
+5000001010000010100000101
-5100001011111101011111011
+0000000000000000000000000
-01000000011111111(不存在)
-128无法表示无法表示10000000

再看几个常用负数补码的速查:

十进制8位补码
-111111111
-211111110
-311111101
-411111100
-911110111
-12810000000

你会发现一个规律:负数补码从11111111开始往下减,数值越小补码越小,这和原码直觉不一样。原码里-1是10000001,-127是11111111,越小的负数原码反而越大;补码里-1最大,-128最小。这背后就是“补码和绝对值大小对齐”的模运算设计,你理解这一步以后,再做负数比较大小的选择题就顺手了。

3. 用C++亲手验证补码,比死记硬背强十倍

3.1 利用bitset打印整数的二进制形式

原码反码补码的纸面换算练多了,还是要回到编译器里看一眼,印象才牢。C++里有个特别适合干这事的工具std::bitset,能把一个整数的二进制位直接打出来。

我写了一个很简单的验证程序:

#include <iostream> #include <bitset> using namespace std; int main() { int a = 5; int b = -5; int c = -128; cout << bitset<8>(a) << endl; // 00000101 cout << bitset<8>(b) << endl; // 11111011 cout << bitset<8>(c) << endl; // 10000000 cout << bitset<16>(-5) << endl; // 1111111111111011 return 0; }

运行结果验证了刚才所有手工计算。需要注意一个小细节:bitset<8>(b)本质上是在对-5的无符号低8位做截断输出,但恰好把8位补码展示出来了。如果在真实环境里用int(通常是32位)去查看负数,你会看到一大串1开头的高位,因为int类型本身就是32位补码,-5存的是0xFFFFFFFB。这正好说明一个事实:你在代码里写的每个负数,从加载进内存那一刻起,就已经是补码了。

有的同学会问:考试也不让写代码,验证它干嘛?我的经验是,动手验证过一遍“补码长什么样”,记忆的牢固程度比单纯背表高得多,而且以后再看位运算结果,大脑会自动浮现那串二进制,不用临时算。

3.2 位运算符其实全是在补码上做手脚

C++里常见位运算符有六个:&、|、^、~、<<、>>。它们操作的对象,全部是内存里的补码。很多人算~5会下意识想“5变成-5”,实际上是对补码逐位取反:

5的32位补码低8位:00000101 ~5逐位取反后: 11111010

再看11111010的补码对应值:符号位是1,所以是负数;用权重法算,-128 + 64 + 32 + 16 + 8 + 2 = -6,所以~5 == -6。你还可以快速验证:~(-6)等于5,这一对互为取反。这也说明按位取反和“求一个数的相反数”是两码事,后者通常要取反加1。

<<和>>也和补码有很强关联。左移操作会丢弃最高位,低位补0,对补码上的正数来说就是乘2,对于负数,左移时可能会改变符号位,所以不能无脑说“左移就是乘2”,只能说“在不溢出的前提下,左移一位相当于乘2”。右移分两种:算术右移和有符号右移往往符号扩展,即负数右移后高位补1,保证结果仍是负数,比如-8 >> 1 == -4;逻辑右移高位补0,主要用在无符号数上,对应unsigned类型。

位运算符的速查表我建议保存下来:

运算符作用示例结果
&按位与5 & 31
|按位或5 | 37
^按位异或5 ^ 36
~按位取反~5-6
<<左移5 << 110
>>右移-8 >> 1-4(算术右移)

做题时最常踩的坑是“负数的右移结果”和“按位取反结果”。你一旦理解“补码上操作”,这些坑就都不是坑了。

4. GESP三级实战题目与拆解过程

4.1 会写负数补码,更要会从补码反推十进制

GESP三级的笔试部分,最常见的题型就是给出一个负数,让你求它的8位补码;或者反过来,给一个补码,让你判断十进制值。这两类都必须稳。

先看正向题:“写出-9的8位补码”。老老实实分三步走:

  1. 9的8位二进制是00001001。
  2. 按位取反(或者视为负数原码符号位不动、数值位取反)得到11110110。
  3. 加1得到11110111。

所以-9的8位补码是11110111。这种题别偷懒跳步,跳一步就可能漏符号位处理。

再看反向题:“已知8位补码10001000,求它对应的十进制值”。我推荐你用“符号位权重法”,把最高位当作-128,其他位正常按二进制权重相加:

10001000 = -128 + 8 = -120

需要解释一下:在这个算法里,最高位的权重是负的-128,其余权重的和正好是120,两者相加就是-120。相比“先看符号位,取反加1求绝对值”的传统方法,权重法对选择题特别快,而且不容易出错。如果你更习惯传统方法,也可以这样算:10001000取反得01110111,加1得01111000,这个数的十进制是120,因为它是负数,所以最终就是-120。两种方法互相可以作为验算。

再给一个容易迷惑的点:同样的8位二进制10001000,如果当成无符号数,它的值是136;当成有符号补码,值是-120。考试常在一个题目里切换视角,你在读题时一定要先确认“这道题说的是有符号数还是无符号数”。

4.2 位运算题目现场推演:这题看起来是计算,其实考的是补码

位运算题在GESP三级里非常常见。比如“~5的十进制结果是多少”“-8 >> 1等于多少”,还有把位运算和逻辑判断混在一起的题目。我拿一道典型题拆一遍:

题目:在C++中执行int x = ~(-6); cout << x;,输出结果是什么?

按补码推:

  1. -6的8位补码是11111010(6=00000110,取反11111001,加1得到11111010)。
  2. ~(-6)逐位取反得到00000101。
  3. 00000101对应十进制5。

所以输出是5。这道题的亮点在于,它考察的是“按位取反”和“求相反数”的区别。如果不理解补码,容易误以为~(-6)等于6或-5,其实答案是5。

另一道经典题:“在32位int下,-1 >> 1的结果是什么?”-1的补码全32位都是1,算术右移后高位继续补1,结果还是全1的补码,对应-1。也就是说,-1无论右移多少次,只要不因为移位位数超过类型宽度产生未定义行为,结果都是-1。这正是补码设计带来的特性,负数算数右移是向负无穷方向取整:-8 >> 1 = -4,-3 >> 1 = -2,和整数除以2向零取整不一样。

做题时记住一条:位运算考的不是运算本身,而是在补码上的运算结果。

5. 常见问题与避坑指南

5.1 做题容易踩的五个典型坑

这些年我见过不少同学在补码上翻车,翻来覆去基本都是这几个点,列出来你对照一下:

第一个坑:混淆“按位取反”和“求补码的取反加1”。求补码时,很多人见到“取反加1”就直接对所有位取反再加1,但忘了符号位在取反后会被加1影响。其实处理负数补码时,更喜欢用“符号位不动,数值位取反,再加1”的方式,不容易出错。而在位运算~中,符号位也会被取反,这是完全不同的操作,千万别混。

第二个坑:0的补码问题。0的补码永远是全0,不存在10000000表示负零的情况。看到10000000,要么是-128,要么按无符号数是128,绝对不要写“负零”。

第三个坑:忘记8位补码的范围边界。最小是-128,最大是127。有些人算到10000001,以为是-129,不对,这是-127。因为补码是从-128到127连续编码的,不是按对称区间来的。这个不对称性害得很多人丢分。

第四个坑:右移负数时,结果不是简单的“除以2向上取整”。算术右移时,负数高位补1,所以-3 >> 1 = -2,不是-1。考试一旦考这个,最好直接把补码列出来看一眼,比如-3的补码11111101右移一位高位补1变成11111110,这是-2的补码,结果一目了然。

第五个坑:有符号数和无符号数比较的陷阱。GESP三级偶尔会考“int a = -1; unsigned int b = 1;,a和b谁大?”。按正常想法-1 < 1,但在C++里,比较时会先把a转成无符号数,-1变成0xFFFFFFFF,结果反而大于1。这和补码解释是紧密联系的:因为负数在内存里的补码是一个很大的无符号数。考试如果遇到,先想类型转换规则,别拿数学直觉硬套。

5.2 考场上快速检查补码题的小技巧

分享几个我在实际刷题中反复用、也确实见效快的检查方法。

第一个技巧:用“补码的补码是原码”快速自检。比如算完-5的补码是11111011,想验证对不对,就把11111011当作补码再求一次相反数:逐位取反得到00000100,加1得到00000101,正是5的原码。能回到原值,说明前面没算错。这个技巧强烈建议形成肌肉记忆。

第二个技巧:用权重法直接口算补码对应的十进制。最高位权重是负的,其他位正权重。看到一个补码,立刻口算。例如11000000,权重就是-128 + 64 = -64,非常快。这个方法在选择题里几乎等于开挂,因为选项往往只差一两步,算得快就能留出时间做后面的大题。

第三个技巧:上机实践时用bitset打印小整数的补码,顺手验证位运算。特别是~、>>这些容易踩坑的运算,多打印几次,大脑会建立“补码位形态”的直觉。等到考试时虽然没有编译器,但这些视觉印象会帮你快速判断。

第四个技巧:把所有常用负数的补码表抄一遍。我备考的时候把-1到-15、-128的8位补码都手写过几遍,写多了以后,出题人给我任何一个补码,我扫一眼最高位和最低位就能猜出大概范围。手写绝对不是无用功,它能直接提升做题速度。

最后再分享一点个人体会

光看公式永远记不牢,我自己学原码反码补码的时候,就是一边看书一边在C++里打印二进制,打了几十个数的补码以后,突然就通了。后来做GESP三级的位运算题,几乎不用草稿纸,盯着题目脑子里直接浮现补码序列。建议你备考时也这样做:每一道涉及负数的题,都顺手在本地跑一遍验证,看输出和你手算是不是一致。这个习惯往前说能帮你过三级,往后说对理解C++的类型转换、内存表示、算法里的位压缩都是打底子。下一篇如果继续写GESP三级语法知识,我会接着展开位运算的完整用法和类型转换陷阱,到时候这套补码知识全都会用上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询