【由char型取值范围引发的思考】(整形提升,127+1=-128)

众所周知,C中有符号char型的取值范围是-128——127,无符号char型的取值范围是0——255。而在这些基础知识点背后,有着许多更加底层的东西值得我们去挖掘与思考。

本篇文章将从数据在内存中的存储形式展开,解释为什么char型取值范围如上所述,并回答一个经典的面试题:为什么127+1=-128

一、 数据类型的基本分类

整型分类
字符型:char unsigned char
短整型:short [int] unsigned short [int]
整型: int unsigned int
长整型:long [int] unsigned long [int]
浮点型分类
单精度浮点型 : float
双精度浮点型 : double

二、占用存储空间:

char
  • char类型通常占用1个字节(8位)的内存空间。
  • 存储的数据范围为-128到127(有符号char)或0到255(无符号char)。
short
  • short类型通常占用2个字节(16位)的内存空间。
  • 存储的数据范围为-32768到32767(有符号short)或0到65535(无符号short)。
int
  • int类型的大小通常为系统的字长,例如在32位系统中占用4个字节(32位),在64位系统中占用8个字节(64位)。
  • 存储的数据范围为-2147483648到2147483647(有符号int)或0到4294967295(无符号int)。
long
  • long类型通常占用4个字节(32位)或8个字节(64位)的内存空间,取决于系统的字长。
  • 存储的数据范围与int类型相似,但更大。
float
  • float类型通常占用4个字节(32位)的内存空间。
  • 存储的数据范围为IEEE754标准中的单精度浮点数范围。
double
  • double类型通常占用8个字节(64位)的内存空间。
  • 存储的数据范围为IEEE754标准中的双精度浮点数范围。

三、数据在内存中的存储

1、整型在内存中的存储

原码、反码、补码
整型分为符号位和数值位,符号位为0表示正数,为1表示负数。
对于正数来说,原码、反码、补码均相同,求出原码即可
对于负数来说:原码:直接将数值按照正负形式转为二进制就是原码
反码: 符号位不变,其他位置数按位取反
补码: 反码+1得到补码

对于整型来说,数据在内存中存储的是补码

2、大小端字节序

大端存储:是指数据的低位保存在内存的高地址中,数据的高位保存在内存的低地址中
例如,十六进制数0x12345678,在内存中的存储顺序是:12 34 56 78。

小端存储:是指数据的低位保存在内存的低地址中,数据的高位保存在内存 的高地址中
例如,十六进制数0x12345678,在内存中的存储顺序是:78 56 34 12。

3、浮点数的存储

根据国际标准IEEE(电和电程协会)754,任意个进制浮点数V可以表成下的形式:
在这里插入图片描述
对于32位的浮点数,最⾼的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字M
在这里插入图片描述
对于64位的浮点数,最的1位存储符号位S,接着的11位存储指数E,剩下的52位存储有效数字M
在这里插入图片描述

4、char类型的存储

​ 无符号char的范围是0——255,有符号char的范围是-128——127。明明是字符类型,为什么还会跟整形一样用数字来规定范围呢?实际上,char类型在内存中跟整形一样,也是以二进制形式存储,它只占一个字节,所以是八位二进制数。最高位是否作为符号位的问题将char类型分成了无符号char和有符号char,范围也因此不同,将这些二进制数转化为十进制,就得到字符对应的ASCII码值。也就是说char类型存储的实际上是字符的ASCII码值。当我将一个字符存储进char类型变量时,就是将它的ASCII码存进了char变量。

那么在这里讨论一下int与 char在scanf读入,print打印,以及互相赋值存储上的问题

  • 关于scanf,它会读取缓冲区一个标准光标大小的内容,这部分内容会以什么形式存储进数据取决于占位符。比如说我在缓冲区打一个一个1,用%c读取就是视为字符‘1’,会将其ASCII码(49)存储,注意,此时的49是八位二进制形式;而如果以%d读取,就会视为整数1存储(三十二位二进制形式)。但要注意,不要出现以下这两种写法:

    int a;
    scanf("%c",&a);
    
    char a;
    scanf("%d",&a)
    

    这两种写法违背scanf的使用约定,不满足其在%d或%c时的期望参数,这会导致未定义行为,例如存储进a的值是随即乱码。

  • 至于打印,则是根据占位符将变量中存储的数据打印出来。比如我将一个char类型以%d打印,就是将其中存储的八位二进制视为整形打印,会发生整型提升;如果将整形以%c打印,会将其中存储的32位二进制数截取最低八位,然后将其视作ASCII码,打印出对应的字符。

  • 如果将int类型赋值给char,会发生截断,取最低八位存储,相当于ASCII码。

  • 如果将char类型赋给int,会发生整型提升。

四、整型提升

谈完了数据的存储,我们再来了解一下什么是整形提升。

1、整形提升概念

C的整型算术运算总是至少以缺省整型类型的精度来进行的。

为了获得这个精度,表达式中的字符(char)和短整型(short)操作数在使用之前被转换为普通整型,这种转换称为整型提升。

2、整形提升规则
  • 对于有符号数(char, signed char ,short, signed short)整形提升转化成标准长度(int)时,在左端补得是最高位(符号位),正数补0,负数补1

  • 对于无符号数(unsigned short ,unsigned char),在左端补得就是0。

在这里先补充一下char型存储的相关知识并对整形提升这一概念进行举例:

char型占一个字节,即八个比特位,无符号char范围是0——255,而有符号char范围是-128——127,这是因为有符号char的最高位是符号位(0表示整数,1表示负数)。

当我们定义一个char型变量并将一个整形赋给它,然后用%d打印出来时:
在这里插入图片描述
127是一个整形数字,它的原码,反码,补码都是00000000000000000000000001111111
char只存储一个字节,所以截断得到01111111并存储在a的八个比特位里。
当使用%d对char型的a进行打印时,就会发生一个整型提升,a是有符号char型变量,所以按照规则,在最高位前面补0直至32位,重新得到了127的补码00000000000000000000000001111111,这就是整形提升。

另外,在char型进行计算时,也会发生整形提升:
在这里插入图片描述
注意这里的计算逻辑一定要清晰:
* 计算时先提升a和b为整形,再将两个整形相加得到一个新的整形。
* 再将这个整形放进char型变量c中,发生上一个示例中的截断现象,仅剩八个字节存储在c中。
* 使用%d打印c,再一次发生了整形提升,将c提升为32位整形并进行打印,输出一个整形的打印值。

其实,由上述内容不难发现,整形提升实际上就是将一个只有8位的二进制数强行视作32位二进制数的过程,这一操作的目的是为了处理一些对char类型变量施以整形操作的行为,例如前面提到的将两个char类型相加,char类型是字符类型,两个字符怎么相加?所以将它们都提升为整形,再进行加法操作。
**实际上,一切将char类型视作整形的操作都要用到整形提升,**就连char类型的范围这一定义都与整形提升有关,一个字符类型的范围为什么会是整形区间?就是将其化作了整形来划分的。一个字符就8位,最高位是否为符号位决定了范围是-128到127还是0到255,超出这一范围的整形数字被当作char 类型存储时会发生截断,只取最低八位。
一个char 类型被视为整形后得到的数字就是它的ASCII 码,实际上,无符号char的128——255和有符号char的-128——-1都没有对应的符号,他们在内存中的二进制数是一样的,是否有符号位影响的是整形提升后整形的正负,符号本身是没有正负的。

五、经典问题:为什么127+1=-128?

相信经过上面的两个示例,大家对于整形提升已经有了一定的理解,但是大家如果仔细看代码的话,应该也已经发现了上述示例二的奇怪之处:
127+3=-126 ???

这是什么原理呢?

这个问题,就不得不回到我们的示例一来看。当我们将整形127赋给char型的a时,因为截断,所以只取了最低的八位进行存储,也就是说除了最高的符号位以外,只有七位可以表示数值,最大的七位二级制数就是127。因此有符号char的范围才是-128——127。

那么,当存储数值大于127或者小于-128时又该怎么办呢。
我们先多列几个测试样例:
在这里插入图片描述
按照这个规律不难发现,char的取值其实可以看作一个圆,这种现象被称为数值回绕,是指在计算中,数值超出了某个数据类型的表示范围时,数值会“回绕”到该范围的另一端,产生不符合预期的结果。
有符号char的范围
相应的,无符号char的取值规律是这样的:
在这里插入图片描述

最后,回到最开始那个有意思的式子:127+1=-128。
由于127以二进制存储的后八位是01111111,数据的相加实际上是补码相加,所以127和1都整形提升成32位二进制位进行相加,所得结果取最低八位是10000000。按照最高位是符号位来看,这个10000000是-0吗?或者说它就是-128,-128取代了-0的位置?
我在网上看到了许许多多的解释,都有些摸棱两可,难辨对错,在这说下我自己的看法:
数据在内存中存放和加减的都是补码,127的补码+1得到的10000000也是个补码,这个数被存储在一个char型的变量中,当被以%d打印的时候发生整形提升,对于补码10000000进行补位,得到11111111111111111111111110000000,化成反码为10000000000000000000000001111111,化为原码为10000000000000000000000010000000,正是-128。
这也正好解释了数值回绕现象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值