当我们在刷抖音或者其他短视频平台时,可能会时不时(总是,我相信大家肯定是不会被外表骗到的那一类人ヾ(●゜ⅴ゜)ノ)刷到各种帅哥美女的视频,或者我们在学校里看到帅哥美女时,如果我们只是看看的话,浮于表面就可以了。但我们如果起了爱慕之心,想要立即就地获得对方的所有资料的时候,我们就不会止于外表,而是会从人品,背景等各个方面看待这个人。
对于数据的存储也是如此,如果我们只是单纯的想用他们,那么掌握用法就可以了,但如果我们想精进自己,了解数据存储的各个方面,那么就不得不谈到最基本的两个数据类型在内存中的存储实质:整形与浮点型。
我们都知道我们的计算机储存数据是存储他的二进制,整数的二进制有三种表示方式,即:原码,补码,反码。三种表⽰⽅法均有符号位和数值位两部分,符号位都是⽤0表⽰“正”,⽤1表⽰“负”,⽽数值位最⾼位的⼀位是被当做符号位,剩余的都是数值位。
正整数的原、反、补码都相同。
负整数的三种表⽰⽅法各不相同:
原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。
而对于整形来说:数据存放内存中其实存放的是补码。
这时候就会有好奇心浓的铁汁问到:“那为什么整形是以补码存放到内存中的呢?”
原因有以下几点:
- 使⽤补码,可以将符号位和数值域统⼀处理
- 同时,加法和减法也可以统⼀处理(CPU只有加法器)
- 此外,补码与原码相互转换,其运算过程是 相同的,不需要额外的硬件电路
那么在了解了整形的存储实质,我们接下来看一个有趣的细节:
我们可以看到a的地址在内存中是倒着放的,这是什么原因呢?
其实超过⼀个字节的数据在内存中存储的时候,就有存储顺序的问题,按照不同的存储顺序,我们分 为⼤端字节序存储和⼩端字节序存储,下⾯是具体的概念:
- ⼤端(存储)模式:是指数据的低位字节内容保存在内存的⾼地址处,⽽数据的⾼位字节内容,保存在内存的低地址处。
- ⼩端(存储)模式:是指数据的低位字节内容保存在内存的低地址处,⽽数据的⾼位字节内容,保存在内存的⾼地址处。
上述概念需要记住,⽅便分辨⼤⼩端 d=====( ̄▽ ̄*)b ,这里教大家一个简单记忆的口诀(博主自创,不喜勿喷哦~):
胸怀大的贤士(大端)喜欢将低价值(低位字节)的东西放在高处(高地址),而将高价值(高位字节)的东西放在低处(低地址),以此与他人无争;
胸怀小的富人喜欢将高价值(高位字节)的东西放在高处(高地址),而将低价值(低位字节)的东西放在低处(低地址),以此展示自己的富有。
那我们该如何判断一个机器是大端还是小端存储呢?恒然可以调试看内存分布情况,但如果我们在面试的时候,面试官让我们笔写一段程序来判断呢?
—————— ( *・ω・)✄╰ひ╯————————————
—————— 分界线我啊是为了让你不要偷看答案的哦~ ————
#include
int check_sys(int a)
{
if (*(char*)&a == 1)
return 1;//是小端
else
return 0;//是大端
}
int main()
{
int a = 1;
printf("%d", check_sys(a));
return 0;
}
看的明了吗,铁汁们?(´・_・`) ,接下来让博主来简单的解释一下吧:
解释完了,都懂了吧?○( ^皿^)っHiahiahia…
好好好,别骂了别骂了,简单说一下:1 是个整形,占四个字节,将整形 1 强制类型转换为 char* 类型后再取出他的地址 得到的就是第一个字节 ,小端机器中就是01(即1),大端机器中就是00(即0)
那接下来我们来谈谈浮点数在内存中存储的实质
众所周知,浮点数家族包括: float、double、long double 类型。那既然整数是二进制存储的,那么浮点数是不是二进制存储的呢?
答案是肯定的,那这个时候博主又有坏心思了,问一下各位同志们,5.5的二进制是个啥?
这里博主就直接给出答案和解释了:5转换成是101,那么5.5的二进制就是101.101。
是个人都能看出来答案错了吧^V^(不然博主坏心思加粗的目的是什么)。让我们一步一步来,回到十进制转二进制的方法:第一位的权重是2 ^ 0,第二位是2 ^ 1,那么,小数点后第一位的权重就是 2 ^ (-1),小时点后第二位就是 2 ^ (-2),以此类推,推算可以得到5.5的二进制就是101.1了。
这时候又有爱动脑筋的铁柱子举一反三了:“既然这样的话,3.3 不就无法用二进制表示了吗?” 我滴妈呀,这都可以被你想到!屏幕前的你也太聪明了吧! 对的,计算机中有些浮点数无法精确保存,3.3 就是其中一个例子。
好的,本篇博客到此为止,谢谢各位的观看与支持!再见。
——————————————————————
*´∀`)´∀`)*´∀`)*´∀`) 以为这就结束啦?!怎么可能对吧?没点爆炸性的知识这篇博客还有啥意思。
那就来一道题目呗┏ (^ω^)=☞
#include
int main()
{
int n = 9;
float *pFloat = (float *)&n;
printf("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
*pFloat = 9.0;
printf("num的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
return 0;
}
答案是啥 答案是啥?9 ,9.000000 ,9 ,9.000000?哈!如果你也是这个答案的话那么得好好看,好好学啦!
好的,接下来正式讲一下浮点数的存储实质:
根据国际标准IEEE(电⽓和电⼦⼯程协会) 754,任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:
• V = (−1) ^ S * M * 2 ^ E
• (−1)S 表⽰符号位,当S=0,V为正数;当S=1,V为负数
• M 表⽰有效数字,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
前⾯说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中 xxxxxx 表⽰⼩数部分。 IEEE 754 规定,在计算机内部保存M时,默认这个数的第⼀位总是1,因此可以被舍去,只保存后⾯的 xxxxxx部分。⽐如保存1.01的时候,只保存01,等到读取的时候,再把第⼀位的1加上去。这样做的⽬ 的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第⼀位的1舍去以后,等于可以保 存24位有效数字。
这意味着,如果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。
除此之外,还有两种特殊情况:E为全0 和 E为全1,其实这就相当于两个极端:我们只需要记住
ok,那我们来回顾一下刚刚的题目:
第一个:第一个打印9应该没问题
第二个:使用指针强制将整形 9
0000 0000 0000 0000 0000 0000 0000 1001
转换为浮点型,套刚刚的公式我们可以得到9的E项全为0,那么这个数就是接近无穷小的,打印出来的值就是0.000000。
第三个:浮点数9.0 等于⼆进制的1001.0,即换算成科学计数法是:1.001×2^3 所以: 9.0 = (−1) ∗ 0 (1.001) ∗ 23 , 那么,第⼀位的符号位S=0,有效数字M等于001后⾯再加20个0,凑满23位,指数E等于3+127=130, 即10000010 所以,写成⼆进制形式,应该是S+E+M,即
0 10000010 001 0000 0000 0000 0000 0000
这个数是多少,不会算?怕啥?俺们程序猿有自己的计算器 是不是莫名的有一股骄傲油然而生了?
第四个:打印浮点数9.000000应该也没有什么问题吧?(*゚ー゚)
对照一下答案,博主没讲错(真好)
本篇博客也就到此为止了,送大家一碗鸡汤,勉励自己以及这世界上所有追逐梦想的赤子趁年华尚好努力提升自己,莫欺少年穷!