🎬 乀艨ic:个人主页
⛺️大家可能又开学或上班了吧,祝大家学习和工作都顺利呀~
⭐️一起来看看这次的博客吧~
☀️前言
大家在学习编程语言时常常会使用各种不同的变量,那么大家有没有想过这些不同的变量在数据中是如何存储的呢?计算机又是如何处理这些不同类型的变量呢?下面我们以C语言为例带大家来具体了解一下不同类型的变量。
☀️变量类型介绍
首先,让我们来列举一下C语言中常见的变量类型:
char //字符数据类型
short //短整型
int //整形
long //长整型
long long //更长的整形
float //单精度浮点数
double //双精度浮点数
🌙类型归类
以上的类型其实我们可以主要将他们分为整型家族跟浮点型家族。
整型家族
char
unsigned char
signed char
short
unsigned short [int]
signed short [int]
int
unsigned int
signed int
long
unsigned long [int]
signed long [int]
注:[]表示可以省略的部分,例如C语言中你打long跟打long int实际上是一样的。
浮点数家族
float
double
long double
可能会有人疑惑,那么字符数据类型算什么呢?这里我们也是将他规划到整型里面了,具体是为什么呢,接下来也会为你做出解释~
当然C语言中也有像 构造类型,指针类型与空类型等,这里就暂不阐述了。
🌙类型大小
现在让我们看一下上述类型所占的字节空间大小:
printf("%d\n", sizeof(char)); //1
printf("%d\n", sizeof(short)); //2
printf("%d\n", sizeof(int)); //4
printf("%d\n", sizeof(long)); //4
printf("%d\n", sizeof(long long)); //8
printf("%d\n", sizeof(float)); //4
printf("%d\n", sizeof(double)); //8
printf("%d\n", sizeof(long double));//8
这是VS2022环境下的变量大小,64位跟32位机器的变量大小大小一样。当然如果你使用其他的编译器,long跟long double的大小可能跟我不太一样,可能分别是8跟16,这是由于C语言标准规范中只定义了long的大小是≥int,long double同样,所以不同的编译器就可能产生不同的结果,不过这个都无伤大雅。
☀️整型在数据中的存储
认识了整型变量,那接下来就让我们看看他们在数据中是如何存储的。
在谈论这个问题之前,我们先认识一些新的概念。
🌙原码、反码、补码
那么,什么是原码,反码,补码呢?
首先我们需要知道以上三种表示方法均由符号位和数值位两个部分组成。
符号位都是用0表示“正”,用1表示“负”,而数值位的内容三种表示方法则各有区别。
原码
直接将数值按照正负数的形式翻译成二进制就可以得到原码。
反码
将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码
反码+1就得到补码。
当然有的书上可能写了一些公式来表达计算方法,但是本质上就是如上的内容,已经可以不用记那么冗长的公式啦~
根据上述的定义,我们可以得到下面的省流结论:
正数的原、反、补码都相同。
负整数的三种表示方法各不相同。
对于整形来说:数据存放内存中其实存放的是补码。
可是为什么呢?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;
这是什么意思呢?让我们举个例子来实际说明:
int a = 10;
//00000000 00000000 00000000 00001010 原码
//00000000 00000000 00000000 00001010 反码
//00000000 00000000 00000000 00001010 补码
int b = -10;
//10000000 00000000 00000000 00001010 原码
//11111111 11111111 11111111 11110101 反码
//11111111 11111111 11111111 11110110 补码
int c = a + b;
//10000000 00000000 00000000 00010100 原码相加时c的值
//00000000 00000000 00000000 00000000 补码相加时c得值
从上面的代码可以看出来,如果我们用补码来进行加减法时,我们竟然可以直接让符号位也参与运算,这个时候得到的答案也是对的,但是原码显然就不可以,这就是为什么用补码存储的原因之一。
同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
这又是什么意思呢?
让我们补充一个知识,其实,CPU内部只有加法器,而其他的运算则基本通过加法进行模拟。同时,原码变补码跟补码变原码的过程其实是一样的,比如说:
int a = -10;
//10000000 00000000 00000000 00001010 原码
//11111111 11111111 11111111 11110101 反码
//11111111 11111111 11111111 11110110 补码
其中我让原码变成补码是除了符号位按位取反然后加1,实际上我对补码进行除了符号位按位取反再加1,就同样可以得到原码,大家也可以自己试试哦~
这样的话我们在CPU中就不需要额外的硬件电路节省资源。
下面让我们在编译器中亲自验证一下以上的说法。
当然因为我们使用的小端机器,低位字节的内容会被存储在低地址处,所以在视觉上可能就显示出倒着存储的效果。
🌙字符类型
我们在分类时选择将字符类型变量归到整型家族,如今我们来解释一下原因。
首先我们都知道,char类型在存储的时候本质上是存储的字符的ASCII码值。而ASCII码在存入char变量的时候也是按整数的补码储存的,这意味着,如果你这么写代码:
char c = 10;
你的编译器甚至有可能不会报警告。
而char的大小只有一个字节,在VS2022的环境下char默认是有符号的char,也就是说他的最大值是127,而谈到这里,下面我们需要再引入一个概念——整型提升。
🌙整型提升
那么什么是整型提升呢?
C语言的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
什么意思呢?简单地说当我们使用一个小于4个字节的整形变量时,它会被整型提升到4个字节然后在运算,这里指的就是字符和短整型这两个。
⭐整型提升的意义
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令。
所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
//实例
char a,b,c;
...
a = b + c;
b和c的值被提升为普通整型,然后再执行加法运算。
加法运算完成之后,结果将被截断,然后再存储于a中。
⭐整型提升的规则
整形提升是按照变量的数据类型的符号位来提升的
//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//无符号整形提升,高位补0
总而言之就是,有符号变量,正数前面补0,负数前面补1。无符号变量直接补0即可。
如果你想验证以上的说法,你可以试试在char中放一些负数或大于127的数字进行尝试。
- 整型变量的存储
☀️浮点型变量
说完了整型变量在数据中的存储方式,我们来谈一下浮点型,浮点型的存储方式跟整型完全不一样,如果你不信可以像这样进行尝试:
int a = 10;
float b = 5.5f;
printf("%d\n", b);
printf("%f\n", a);
那么你就会得到这样的结果:
当然,当你把这篇文章看完之后,你就可以理解这个原因啦~
🌙IEEE754
根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
- (-1)^S * M * 2^E
- (-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
- M表示有效数字,大于等于1,小于2。
- 2^E表示指数位。
举例来说:
十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2 。
那么,按照上面V的格式,可以得出S=0,M=1.01,E=2。
十进制的-5.0,写成二进制是 -101.0 ,相当于 -1.01×2^2 。那么,S=1,M=1.01,E=2。
IEEE 754规定:
对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。
IEEE 754对有效数字M和指数E,还有一些特别规定。
前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。
这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字。
至于指数E,情况就比较复杂。
首先,E为一个无符号整数(unsigned int)
这意味着,如果E为8位,它的取值范围为0 ~ 255;如果E为11位,它的取值范围为0 ~ 2047。但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即10001001。
根据这个逻辑,你就可以自己验证测试刚刚举的例子为什么是这个结果啦~
- 浮点型变量的存储
☀️结尾
感谢观看,喜欢的话可以给一个大大的三连哦~
期待我们的下次相遇,喜欢的请点个关注再走哦!之后会持续更新更多的内容!
个人主页:传送门