秒懂有/无符号char以%d/%u形式打印的核心

📅 发布时间:2026/10/14 1:45:57
秒懂有/无符号char以%d/%u形式打印的核心
目录引入一原反补1正/负整数的原反补2有符号/无符号3原反补之间的转化①原码到补码②补码到原码二截断和整形提升1截断:2整形提升三%d和%u的区别四有/无符号char的范围1范围的探究2规律的探究五例题讲解1有/无符号char以%d形式打印2有符号以%u打印3signed char范围规律4unsigned 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内整型操作数的标准⻓度。③通⽤CPUgeneral-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 charsigned char则左补24个符号位unsigned则直接左补24个0③当我们整形提升之后我们就不用再管char的类型是哪种了此时若是%d打印则代表整形提升之后的32个位是以有符号的形式去打印则需观察符号位符号位为0代表正整数直接打印当前二进制(补码)对应的十进制即可符号位为1代表负整数需要将此时的32位补码转化为原码再打印~而若是%u打印代表此时是以无符号形式去打印直接打印当前补码对应十进制即可注其实计算机只会存储补码计算也是用的补码而所谓的原码反码都是方便我们人为计算和理解从而创造出来的东西所以上文中任何一处提到的编译器或者计算机会把xx码转化为xx码都是为了方便理解四有/无符号char的范围1范围的探究unsigned char的时候8个比特位每个都能用来表示值的大小所以范围为0~255signed 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 char1-128127short2-32,76832,767int4-2,147,483,6482,147,483,647longLinux 64位8-9,223,372,036,854,775,8089,223,372,036,854,775,807longWindows 64位4-2,147,483,6482,147,483,647long long8-9,223,372,036,854,775,8089,223,372,036,854,775,8072规律的探究我们知道signed char的范围为-128-127unsigned 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~-128127到0考法如下此时如果我们有一个char类型的数组大小为1000考法1题目试图存储0~999的值到这个char数组那么我们只会存储进去0~127-128到-1的值进前256个数组位置后面就是重复再存储该顺序的值进入后面的数组位置考法2题目试图存储-1~ -1000的值到这个char数组那么我们只会存储进去-1~-128127到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则为正数当前补码就是原码直接打印二进制表示的值2552有符号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打印以无符号整形打印所以直接视当前补码为原码直接打印对应的二进制值即可所以是一个很大的值a4294967168②b是有signed char类型此时8位存储的是128的低8位补码1000 0000和a存储的相同后续也是整形提升%u打印所以打印结果和a相同3signedchar范围规律#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~-128127到0的值进前256个数组位置后面就是重复再存储该顺序的值进入后面的数组位置显然此题就是考法2所以我们这个数组肯定会被存满数据只不过规律是-1~-128127到0然后再-1~-128127到0.......而strlen计算的是\0之前的数据\0的ascll码值就是0所以strlen会在第一次遇到0的位置停止每次-1~-128127到0总共是256个此时不算末尾的0则答案为2554unsigned 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的时候此时因为255255仍不停止进行打印后255递增为256对于unsigned char来说其会变成0重新开始递增所以死循环 [ 作者 ] shylyly [ 首次发布 ] 2024.3.6❌ [ 最新修改 ] 2026.10.13 [ 声明 ] 由于笔者水平有限文中难免有疏漏或不妥之处还望读者不吝赐教。