目录
引入:
一:原反补
1:正/负整数的原反补
2:有符号/无符号
3:原反补之间的转化
①:原码到补码
②:补码到原码
二:截断和整形提升
1:截断:
2:整形提升
三:%d和%u的区别
四:有/无符号char的范围
1:范围的探究
2:规律的探究
五:例题讲解
1:有/无符号char以%d形式打印
2:有符号以%u打印
3:signed char范围规律
4:unsigned char范围规律
引入:
初学C语言时,很多人都会被“原码、反码、补码”劝退一波。更麻烦的是,后面学整型截断、整型提升、%d 与 %u 打印时,又会绕回到“原码、反码、补码”这些二进制转换上,一不小心就会出错。所以单独写一篇博客,那这些问题一次性理清楚,也方便自己将来忘记的时候来复习~
一:原反补
1:正/负整数的原反补
重点:对于一个整数(无论正负)来说,其存储在内存中的二进制位,是补码!!
当我们printf打印一个整数,看到的是一个十进制数字,其实本质是编译器自动地把整数在内存中的补码,通过一些方式转化为了原码,再把原码(二进制)对应的十进制打印给我们查看~
上文说过无论正负整数,其在内存中存储的都是补码,但需注意:
对于正整数来说,原反补相同
对于负整数来说,原反补各不相同,所以只有负数才需要去计算反码和补码
❓️:正整数原反补相同,那该理解为这三种都是原码?还是反码或者补码??如果都是原码,那之前说的不管是正整数还是负整数其存储在内存中的都是补码,那正整数的原码作为补码存储在内存中,这不是矛盾了吗?
💡:对于正整数来说,其存储在内存中的二进制位,当然仍是补码!并且应该理解为正整数也有原反补这三种码,只是一样罢了~
❓️:为什么正整数的原反补相同,而负整数原反补不同?
💡:原码的负数做加减很麻烦,所以才设计出反码和补码,让负整数减法能用加法来实现;而正整数本来用原码就能正常参与运算,不需要额外变换,所以规则才规定正整数原反补相同~
2:有符号/无符号
❓️:那在C语言中怎么区分一个整形是正还是负呢?
💡:signed代表有符号,比如signed int,代表有符号整形,而unsigned代表无符号,比如unsigned int代表无符号整形,而在大多数编译器中,int就是signed int的简写。
而有符号整形signed int中的正整数和unsigned int无符号整形,则为正,故其原反补相同
而有符号整形signed int中的负整数,则为负,故其拥有不同的原反补
如下:
注:有符号整形不一定都是负整数,其也有可能是正整数,而正整数的高位符号位为0,原反补相同
3:原反补之间的转化
所以原反补之间的转化计算,只是针对负整数而言的~
①:原码到补码
| 编码 | 规则 |
|---|---|
| 原码 | 将数值按照正负数的形式翻译成⼆进制得到的就是原码 最高位符号位:0 正 1 负; |
| 反码 | 正数同原码;负数:符号位不变,数值位按位取反 |
| 补码 | 正数同原码;负数:反码 + 1 |
一句话:负数从原码到补码,就是“符号位不变,数值位取反,再 +1”。
②:补码到原码
| 方法 | 操作步骤 | 中间是否得到反码 | 适用对象 |
|---|---|---|---|
| 第一种 | 补码-1(得反码),再取反(得原码) | ✅ 能得到反码 | 负数 |
| 第二种 | 补码取反(得某值),再+1(得原码) | ❌ 不能得到反码 | 负数 |
注:两种方法都能准确的得到原码,但是只有第一种在计算的过程中能得到反码
二:截断和整形提升
1:截断
将一个32位int类型的整数存储到8位char类型中,由于char是一个字节大小,对于四个字节大小的整数,会发生截断,只保留整形补码的最后8位(低8位),因为低8位存储的才是有效的信息
2:整形提升
整形提升意义在于:将存储在char中的8位补充到32位
规则(如何提升):对signed char这种有符号char类型,采用左补24个符号位,符号位就是现在的char中的8个比特位的最高位,如截断后现有的char的最高位符号位为1,则左补24个1,反之补0;对unsigned char 这种无符号位的采用直接左补24个0
注:整形提升只有char和short才会进行,此篇博客重点讲char,其次整形提升是编译器自动完成的,我们不用手动去做,但是我们需要理解其中的原理
❓️:那什么时候需要整形提升呢?
💡:只要是涉及到用char或short类型进行计算时,则必须将其进行整形提升再进行计算
底层原因是因为硬件的性质:
①:表达式的整型运算要在CPU的相应运算器件内执⾏,CPU内整型运算器(ALU)的操作数的字节⻓度⼀ 般就是int的字节⻓度,同时也是CPU的通⽤寄存器的⻓度。
②:因此,即使两个char类型的相加,在CPU执⾏时实际上也要先转换为CPU内整型操作数的标准⻓度。
③:通⽤CPU(general-purpose CPU)是难以直接实现两个8⽐特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种⻓度可能⼩于int⻓度的整型值,都必须先转换为 int或unsigned int,然后才能送⼊CPU去执⾏运算
三:%d和%u的区别
%d:以十进制的形式打印有符号整形
%u:以十进制的形式打印无符号整形
所以分别用%d和%u对同一个整数进行打印,结果必然是不同的!
例子:对-1分别使用%d和%u打印
#include <stdio.h> int main() { int a = -1; printf("%d\n", a); // 以有符号十进制打印 printf("%u\n", a); // 以无符号十进制打印 return 0; }解释:
内存中 -1 的补码(32位):11111111 11111111 11111111 11111111
%d:把这一串二进制当作有符号数 → 最高位是1 → 负数 →负数的原反补不同,转换为原码→10000000 00000000 00000000 00000001→ 结果是 -1%u:把这一串二进制当作无符号数 → 当做正整数来看→ 原反补相同→ 当前补码所有位都是数值位 → 结果是 4294967295(即 2³² - 1)
所以之前强调的对于一个整数来说,其存储在内存中的二进制位,一定是补码,这句话至关重要
而%d和%u在考察的时候不会直接打印一个整形这么简单,而是去打印一个char,须知,char要被%d和%u打印的时候,一定会先发生整形提升!!
所以一般套路:先让char接收一个整形,发生截断,然后再用%d和%u去打印char,此时char会发生整形提升,这一套下来,如果不理解原理,那么结果一定是看不懂的!
所有考察情况如下:
解释:
①:大多数编译器上的char就是signed char的缩写
②:整形提升的时候,需先看char 的类型,signed char还是unsigned char?signed char则左补24个符号位,unsigned则直接左补24个0
③:当我们整形提升之后,我们就不用再管char的类型是哪种了!
此时若是%d打印,则代表整形提升之后的32个位是以有符号的形式去打印,则需观察符号位,符号位为0代表正整数,直接打印当前二进制(补码)对应的十进制即可;符号位为1代表负整数,需要将此时的32位补码转化为原码再打印~
而若是%u打印,代表此时是以无符号形式去打印,直接打印当前补码对应十进制即可!
注:其实计算机只会存储补码,计算也是用的补码,而所谓的原码,反码,都是方便我们人为计算和理解从而创造出来的东西,所以上文中任何一处提到的编译器或者计算机会把xx码转化为xx码都是为了方便理解
四:有/无符号char的范围
1:范围的探究
unsigned char的时候,8个比特位,每个都能用来表示值的大小,所以范围为0~255
signed char的时候,8个比特位,只有低7位是用来表示值的大小,最高位符号位是用来表示正负的,所以范围为-128~127
解释:
在下半部分的负数中,除了-128 ,其它的二进制都可以进行补转原去查看值,原码就是右面的红色标注值,但是1000 0000不能!此时我们对其取反+1,得到的原码是0000 0000,因为向高位进1了,1 0000 0000,但char只有8字节,所以此时1舍去了;所以设计者规定,1000 0000对应的二进制表示就是-128!!
本质原因:
1000 0000是一个没有合理原码的负数补码,但对于计算机来说,本来就只有补码,它很平常的就把负数补码1000 0000规定为-128,而因为我们人为创造的原码和反码对于这种特殊的负数补码来说,无法转化出同样长度比特位的原码,所以我们才会说这是规定,发现了不兼容,才给它贴上的规定标签。
所以char的范围是-128~127,同理,这也是为什么下图中的最小值的绝对值比最大值大1
有符号整型范围表(64位系统常见情况)
| 类型 | 字节数 | 最小值 | 最大值 |
|---|---|---|---|
char/signed char | 1 | -128 | 127 |
short | 2 | -32,768 | 32,767 |
int | 4 | -2,147,483,648 | 2,147,483,647 |
long(Linux 64位) | 8 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
long(Windows 64位) | 4 | -2,147,483,648 | 2,147,483,647 |
long long | 8 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
2:规律的探究
我们知道:signed char的范围为:-128-127,unsigned char的范围为:0-255
所以规律如下两图:
解释:因为无符号char,所以8位都表示大小,0~255,清晰明了
解释:
0~127的时候,都是没进位到符号位的时候,当试图128的时候,此时int 类型的128的补码,为:00000000 00000000 00000000 10000000
此时我们存储的是低8位,1000 0000,但是由于我们是signed char,所以高位为符号位,此时我们规定char下的负数补码 1000 0000为-128,上文已经解释过了!此时我们在不断的+1进二进制位中,就会从-128递减到-1,当-1的二级制为再+1,则为0000 0000,循环回到了值为0的时候
所以,某些题目里面,会考这个循环的正序和倒序!
正序:0~127,-128到-1
倒序:-1~-128,127到0
考法如下:
此时如果我们有一个char类型的数组,大小为1000
考法1:题目试图存储0~999的值到这个char数组,那么我们只会存储进去0~127,-128到-1的值进前256个数组位置,后面就是重复再存储该顺序的值进入后面的数组位置
考法2:题目试图存储-1~ -1000的值到这个char数组,那么我们只会存储进去-1~-128,127到0的值进前256个数组位置,后面就是重复再存储该顺序的值进入后面的数组位置
五:例题讲解
1:有/无符号char以%d形式打印
#include <stdio.h> int main() { char a = -1; signed char b = -1; unsigned char c = -1; printf("a=%d,b=%d,c=%d", a, b, c); return 0; }解释:
①:-1截断之后,在abc中存储的都是低8位补码 1111 1111
②:a b本质都是signed char类型,此时马上要被%d以有符号整形打印了,所以二者都要先进行整形提升,提升后得到的补码均为:
1111 1111 1111 1111 1111 1111 1111 1111
也就是32个1,然后该补码对应的原码为
1000 0000 0000 0000 0000 0000 0000 0001
所以打印结果就是-1
③:对于c,其是unsigned char,所以整形提升左补24个0,此时补码为
0000 0000 0000 0000 0000 0000 1111 1111
此时%d打印,判断符号位为0,则为正数,当前补码就是原码,直接打印二进制表示的值,255
2:有符号char以%u打印
#include <stdio.h> int main() { char a = -128; char b = 128; printf("a=%u\n", a); printf("b=%u\n", b); return 0; }解释:
①:a是有signed char类型,此时8位存储的是-128的低8位补码1000 0000,此时即将被%u打印,所以会被整形提升,提升后得到的补码为:
1111 1111 1111 1111 1111 1111 1000 0000
此时%u打印,以无符号整形打印,所以直接视当前补码为原码,直接打印对应的二进制值即可,所以是一个很大的值,a=4294967168
②:b是有signed char类型,此时8位存储的是128的低8位补码1000 0000,和a存储的相同,后续也是整形提升,%u打印,所以打印结果和a相同!
3:signedchar范围规律
#include <stdio.h> int main() { char a[1000]; int i; for (i = 0; i < 1000; i++) { a[i] = -1 - i; } printf("%d", strlen(a)); return 0; }解释:
上文说过~
考法1:题目试图存储0~999的值到这个char数组,那么我们只会存储进去0~127,-128到-1的值进前256个数组位置,后面就是重复再存储该顺序的值进入后面的数组位置
考法2:题目试图存储-1~ -1000的值到这个char数组,那么我们只会存储进去-1~-128,127到0的值进前256个数组位置,后面就是重复再存储该顺序的值进入后面的数组位置
显然此题就是考法2,所以我们这个数组肯定会被存满数据,只不过规律是-1~-128,127到0,然后再-1~-128,127到0.......
而strlen计算的是\0之前的数据,\0的ascll码值就是0,所以strlen会在第一次遇到0的位置停止,每次-1~-128,127到0总共是256个,此时不算末尾的0,则答案为255
4:unsigned char范围规律
#include <stdio.h> unsigned char i = 0; int main() { for (i = 0; i <= 255; i++) { printf("hello world\n"); } return 0; }运行结果:死循环打印 hello world
解释:
因为unsigned char的范围是0~255,当i递增到255的时候,此时因为255=255,仍不停止,进行打印后,255递增为256,对于unsigned char来说,其会变成0重新开始递增,所以死循环
📌 [ 作者 ] shylyly
📃 [ 首次发布 ] 2024.3.6
❌ [ 最新修改 ] 2026.10.13
📜 [ 声明 ] 由于笔者水平有限,文中难免有疏漏或不妥之处,还望读者不吝赐教。