这期博客咱们来讲一讲数据在内存中是如何存储的呢,这期较难,博主在理解的时候也是几近崩溃,今天给大家讲三个内容
咱们先确定一个事实,就是:整数在内存中都是以补码的形式存储的,那么为什么呢?这里引用一下别的文献
原因在于,使⽤补码,可以将符号位和数值域统⼀处理;
同时,加法和减法也可以统⼀处理**(CPU只有加法器)**此外,补码与原码相互转换,其运算过程是
相同的,不需要额外的硬件电路。
其实这个的话,了解一下即可
那么什么是大小端呢?我们先给一段代码
int main()
{
int a = 0x11223344;//这是一个16进制的表达式,11表示一个字节,后面的223344也是,那么他们在内存中的地址排序,是否也是按照从小到大的排序方式排序11 22 33 44呢?
}
我们可以看出,44居然排到了最前面(也就是低地址处),相反,11排到了高地址处。这里,要普及一个概念
0x11223344,11在里面表示高位处,44表示最低位,就像一百一十一这个数字一样,最前面的1落于百位,所以属于高位,最后面的1落于个位,属于低位所以上述的代码调试截图,表达的就是0x11223344的低位(也就是44)落到了地址的地位,这,就叫做小端排序,俗称“低位落低位”,相反,如果0x11223344中的地位(也就是44)落到了地址中的高位,也就是地址的最右边,这,就叫做大端排序,俗称“低位落高位”
那么,我们该进行怎么的操作,才能确认我们的编译器是属于大端还是小端?这就需要借助我们前面所学的指针知识了!
int main()
{
int a = 0x00000001;//假设a为1,如果为大端则排序为00 00 00 01,如果为小端,则为01 00 00 00
int * p = &a;//定义p指针变量,指向a这个整型数据
((char *)p) = 0;
return 0;
}
将p强制转换成char *指针,让其拥有一次只能操作一个字节的权限,如果修改之后,a变成了0,就说明p将最左边01修改成了00,则证明是小端,修改后如果还是等于0,则说明p修改的是最左边的00,则证明是大端
这里其实我们要分为有符号和无符号两种不同的情况来讨论
这里先给大家将一下,为什么char的取值范围为-128~127?
众所周知,char是8个字节那么就从0000 0000说起,这是代表0
0000 0001这是代表1
0000 0010这是代表2,…以此类推,直到
0111 1111这是代表127
有人问这里为什么最高位不能是1,因为这个最高位是符号位,符号位是0代表的是正数,所以正数的上限只能是127,往上加就变成负数了,请跟着我的思路一起来
127往上+1就变成了1000 0000,符号位为1就是负数了,而我们都知道,整数在内存中以补码的形式存储,而打印的却是它的原码,所以我们要把1000 0000转化成原码,这里有个特殊规定,就是1000 0000强制规定为-128,
1000 0000,代表的是-128负数才有原反补概念
1000 0001,前面的往上+1,这里是补码,原码是1111 1111,最高位为符号位 ,代表的是-127
1000 0010,前面的往上+1,这里是补码,原码是1111 1110,最高位为符号位,代表的是-126
最终,会加到1111 1111,这里是补码,原码是1000 0001,最高位位符号位,代表的是-1
1111 1111+1会等于1 0000 0000,char类型只存放1个字节(也就是8个比特位),所以又回到了0,跟上面形成循环了
很遗憾的是,上面讲的是有符号的char,也就是最高位符号位要空出来表示“符号”,真正在发挥作用的只有7个位数,那么无符号char呢?在我讲完上面的这段话之后,你应该有了一点自己的了解了吧
我们将上面的一些讲解复制下来
0000 0001这是代表1
0000 0010这是代表2,…以此类推,直到
0111 1111这是代表127
在0111 1111基础上+1
就变成了1000 0000,在上面的例子中,1000 0000,是负数,而负数要经过转换才能变成原码,但这是有符号char的做法,而无符号char呢?管你有无符号,通通无符号,也就是说1000 0000 直接当作126 最高位的符号位充当数值位,再往上+1呢,
1000 0001,代表的是127
1000 0010,代表的是128
最终,加到了1111 1111,代表的就是255,再往上加就是1 0000 0000,而char只能取8个比特位,所以无符号的char最高只能取到255,
所以unsigned char 的取值范围位0~255
上面的例子换成int型,unsigened int也同样适用,这就是整型在内存中是如何存储的。
浮点数就是带有小数点的数,比如5.5,3.14都是我们所熟悉的浮点数,前面的整数我们都知道是以补码的形式在内存中存储的,那么浮点数呢?
有一个公式:V = (−1)(S)* M * 2(E),括号里面代表的是几次方,例如(-1)(S)就代表(-1)的S次方,2
(E)就代表2的E次方,需要强调的是,这个公式是适用于浮点数的二进制位表示方法的,那么,二进制的浮点数不知道怎么表达怎么办?博主来教你们
这里下面,咱将一下浮点数转换为二进制的做法
拿5.5举例
(1)小数点前的数字,直接转换成二进制,5用二进制表示就是101
(2)小数点后的数字,也就是0.5。我们知道0000 0000,最低位的0,也就是加粗的0,所代表的权重是2的0次方,每次往左,就会增加一次权重,0000 0000,这一位的权重就是2的1次方。那么小数点后的第一位,它的权重就是2的(-1)次方,刚好就是1/2,也就是0.5所以,0.5用二进制来表示就是.1
(3)将小数点前的二进制与小数点后的二进制凑起来,就变成了101.1
这就是5.5的二进制表示方法,为101.1,所以计算机是将101.1这个二进制数存到内存当中,接下来,我们看看计算机是如何操作的。
咱们从小学就认识了科学计数法,上面的浮点数也能转化成科学计数法,101.1用科学计数法表示为1.011*2的三次方,咱们小学的是10的n次方,是因为小学接触的数字都是10进制数字,所以小学的时候科学计数法才是乘以10的n次方,但是咱们现在写的是二进制数字,所以是2的n次方。
int main()
{
float a = 5.5;//5.5转换成二进制为101.1
return 0;
}
1.0112的三次方,前面我们讲到的公式:V = (−1)(S) M * 2(E),在这里可以提取出两个,M代表的是有效数字,也就是1.0112的三次方中的1.011,E就是1.0112的三次方中的三,S就是符号位,5.5是大于0的,所以是正数,S取0;
所以,5.5用上面的公式表示为:(-1)(0)*1.011 *2(3),S = 0,M = 1.011,E = 3,接下来,我们把这三个有重要数字存入到内存中
一个float为4个字节,也就是32个比特位,跟着我的步伐
0 00000000 0000000 00000000 00000000
我将32个比特位分成了三块
(1.)第一块(也就是最左边那个位),用来充当的符号为,也就是S的值,S存0,代表浮点数是个正浮点数,S存1,代表浮点数是个负浮点数
(2.)第二块(也就是中间那八个比特位),用来存放E的值,也就是2的E次方里面的E的值,八个比特位,最大值可以达到255,2的16次方就已经到了42亿多了,所以255是完全没有必要的,而且都是正的,2的负数次方呢?所以这八个比特位也有一部分要来存放负数的E,所以规定:里面的存储值=真实值+127,也就是说E的真实值最大也就为128(128=255-127),最低的值为-127,存储值最低也就0,所以真实值最低也就-127,因为这个E是无符号的,
(3.)第三块(右边最大的那一块比特位),用来存放有效数字,也就是1.xxxxxx,但是所有的浮点数转换成二进制的有效数字全都是1.xxxxx,C语言就规定了,把1.xxxx中的1.去掉了,只保留xxxx存入内存中,等到要取出来的时候,再把1.加上
讲了这么多,我们重新把5.5这个浮点数的重要数字拿出来,S=0,M = 1.011,E = 3
所以5.5存入到内存中的表达形式是
0 10000010 01100000000000000000000存储到内存中的
S E+127 011存进去,不够用0补充
上面讲的是浮点数存入内存的逻辑,下面要讲取出来的逻辑,其实也是挺好理解的
下面是浮点数取出来的逻辑
(1)E有0也有1
这是最正常的情况,按照上面的情况取出来就行
(2)E为全0的情况(E为1-126或者1-1023)
有效数字:直接0.xxxxxxx,不再加上1.。从而表示正负0,以及接近0的很小的数字
(3)E全为1
E全为1,存储值达到了惊人的255,即使真实值也就128,但是1.xxxxx乘上2的128次方是一个接近无穷的数
所以E全为1为了表示无穷大的数