之前对于整数在内存中的存储我们就讲过了在这篇文章中,只不过有点杂,零零碎碎的
https://blog.csdn.net/Easonmax/article/details/134298830?spm=1001.2014.3001.5501?
?现在把之前的知识点总结一遍。
?
对于整数二进制有原码反码补码。
其中内存中存放的都是补码, 而在平时我们写出的,给我们看的,展现给我们的都是原码。
对于有符号的整数最高位使符号位,仅仅代表正负,不代表大小的意思。(但符号位依然会在计算时会参与)无符号就不存在符号位。(这两种类型可能会发生隐式转换,该类隐式转换就是其内存中的每个值不变,仅仅把符号位给变没或者使其存在符号位从而改变大小)
对于整数中的原码 ,当其为正数时,原码反码补码完全一样。
当其为负数时,原码将数值位取反得到反码(符号位不取反)(按位取反操作符是直接全取反),再加一得到补码。 同理补码得到原码可以反过来,也可以同样取反(符号位不取反)加一。
?
字符也属于整形,因为字符是以ascall码值存放(本质ascall码值),ascall码值属于整形,内存为1个字节。所以像'a'这种就是占八个内存的整形,其中用char去创建,char是创建一个八个内存的整形(vs指的是signed char)。
?所以char不只能接受字符,也能接受数字(一定范围),这里要说一下我们通常写出来的代码数字形式都是 signed int形式。
如我们用char? b=20;这里的20其实是 signed int 类型,然后发生隐式转换直接切割高进制位变为char类型再存入到b中(练习二中将会讲到)
?像打印用%c就是直接打印出来'a',而用%d就是打印出来其对应数字97(对于其print具体机制如下)。
printf具体机制是当数据为除float类型外的浮点型或者long long类型时存储的字节大小是8个,其他都为4个。(上图说错了,float类型是存储四个字节,不是八个)而后根据占位符作用再对存储的数据再进行变化。(printf是库函数,内部太复杂) (像你用了%d,后面参数是 unsigned char类型,先会将其变为四个字节(还是无符号,只改变其所占字节大小),然后因为%d是打印有符号整数,所以再将其变为有符号,从而打印出其对应十进制)(之后的练习例题中会用到)??
所以像该char类型打印就是先转化为四字节类型再打印?
char类型能隐式转换为int类型(能互相隐式转换,也就是'a'能隐式转换为97(int类型))
整形类型的数进行计算,都会将其转换为内存为32的整形再去运算(整形类型范围里的数都能隐式转换)
对于较短的内存进行转换成内存为32的整形,为整形提升。
?其规则为以上,现在举个例子。
//负数的整形提升
char c1 = -1;
变量c1的?进制位(补码)中只有8个?特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,?位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的?进制位(补码)中只有8个?特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,?位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//?符号整形提升,?位补0
对于超过四个字节的整数将其变小则是直接切割,没有任何规则 。(适用于任何字节大的转换为字节小的)
所以在整数类型运算中,都是转换为4个字节的数据再去算,这也能很好解释两个字符在运算前要转换为int类型再去算。
(整形就是整数)?
所以这就是对于整数在内存中的存储的总结。这些都是仅限于整型类型。另一个大类浮点数不能用(int和float不能运算,float不是整形,是另一个大类,整形和浮点型不能互相隐式转换,除非强制类型转换否则不能计算)
以前学的少,在以前写的博客上往往会出现一些错误,把字符以及字符串看作独立于整形和浮点型的其余大类,现在学多了。更新一下。
对于单个数据,分为两大类 浮点型和整形 (字符是单个数据,属于整形,不是独立于他们之外的) 。而像字符串,数组,结构体之类的,它们是多个数据的结合,里面含有多个数据,跟前面的单个数据不搭边。
划分了之后,就能更好的学习,所以在对数据在内存的存储的学习中,我们学习完了整数在内存中的存储。接下来就学习另一大块浮点数在内存中的存储。
对于内部字节为多个的单个数据来说,有大小端存储模式
那么为什么会存在大小端存储模式呢??
?举个例子
对于是大端模式还是小端模式取决于我们所用的环境
大部分都是小端模式?,像vs就是 。少部分为大端模式。
大小端模式只作用于单个数据(整形及浮点型),仅仅对它们单个数据内部的字节进行地址排序。
大小端模式不会影响多个数据的顺序排序。如数组,当为大端模式其中第一个数据就放在地址最小处,最后的数据就是地址最大处,逐渐递增,该排序遵循数组自己的规则,而不是遵循大端模式。此外对于数组名代表的是第一个元素的地址,而该地址的值不管其为大端还是小端都是数组最小的地址,并不会为此而改变。 所以以上几点可以证明大小端仅仅是改变了单个数据的内部字节的地址排序,其余什么都不会影响。
字节序全称为字节顺序,有大端字节序和小段字节序两种
?接下来将用一段代码去判断机器的字节序是大端还是小端
#include <stdio.h>
int check_sys()
{
int i = 1;
return (*(char *)&i);
}
int main()
{
int ret = check_sys();
if(ret == 1)
{
printf("?端\n");
}
else
{
printf("?端\n");
}
return 0;
}
?很简单的代码,因为取地址取的是数据中最小的地址不受大小端影响,所以根据该方法可以判断出大小端。
该题要做出来需要我们对数据在内存中的存放的这个大知识点的理解,用到了整形提升和截断,以及对printf的库函数内部理解(前面都讲过)?
??上面该图是其中具体细节变化,因为a是有符号,在截断后内存存放为11111111,a和b都是有符号的,printf存储是以四个字节存储,所以整形提升为11111111111111111111111111111111,%d是打印有符号的整数,其本身就是有符号整数,无需任何变化。
所以补码为11111111111111111111111111111111。打印是打印出原码。所以打印-1.
而c中同理也为11111111,但为无符号所以变为00000000000000000000000011111111,且为无符号,但%d是打印有符号十进制,所以其变为有符号,最高位0变为符号位,内存依然为00000000000000000000000011111111,printf是打印原码,由于为正,跟补码一样,所以打印出255(原码是在屏幕上展现给我们看的明面的东西;补码是实际存在内存上的东西,计算都是补码计算,暗面上的东西)
所以打印结果为-1,-1,255.
上一篇文章已经说过一遍的知识点,现在再强调一遍。?
?当一个数据为signed char类型时 补码为10000000时 原码值为-128,这个为-128看起来不符合其规律,的确,这是特殊规定,我们只需要记住,所以其signed char范围为-128-127。(多了个补码值为10000000原码值为-128导致的结果)
此外,还补充一些点,unsigned? char类型范围为0-255.?(很容易就可以求出)
当使其等于的值超过范围时,就会隐式转换从而变为符合其范围的值,且该范围的值符合一个循环(可以理解为圆圈)
当然不只是上述的?signed char 和unsigned char有以上循环,其他像short,int,long所有类型只要它们有范围,就都会有循环(区别在于循环较大还是较小)。
当然对于signed char为10000000时为-128这个特殊规定,对于其他有符号的如signed? int也同理
10000000000000000000000000000000当作-2147483648.对于有符号的类型都有这个特殊规定
?%d是打印出十进制有符号整数。%u是打印出十进制无符号整数。
而现在有以下该题,通过我们之前学习的内存知识去做该题?
该图为-128的最终结果为:294967168,按我上面说的那些知识点去做其实很简单就能做出来,
为128同理,最终答案一样,且后面过程一摸一样。 (当你学通了就很简单了)?
较为简单,不过多叙述(还不理解建议去看我前面说的知识点)?
第一个结果为死循环打印hello world ,因为最大为255,再加一就由于范围会变为0;所以死循环。
第二个结果为死循环打印。如下图(不只是打印这些,后面还会死循环打印4294967293等等),因为变为0之后减1由于为无符号且有一定范围所以减一变为最大429496795.从而死循环?
?
其实也很简单,第一个打印以前就讲过,想必都知道。就不讲了打印4(十六进制形式)
第二个有点考虑理解,首先前提我们是x86环境,内存为4个字节,所以在转换为int后将其由地址形式转换为整形,从而加一是真正使其数值加一,而后将其再转换为地址就是指向下一个字节。从而如下图就是打印出2000000(十六进制形式),(前提还需要系统为小端存储模式)
?
这些题都涉及了整数在内存中的存储以及计算,关于这些知识点在前面我已经全部说明完毕,所以只要你搞懂了这些知识点做这些题目就很简单。?
到这我们的整数在内存中的存储就结束了,接下来将给大家讲述单个数据另一大块:浮点数在内存中的存储。
?我们自己写的浮点数如1.24等都为double类型,所以如果用float去接受1.24,我们为了防止其在隐式转换中发生一些错误,通常会把1.24写为1.24f。
浮点数在内存中的存储都是以二进制形式存储。所以就有以下存储方式
先将其 转换为二进制,再将其转换为该格式
?
?对于32位的浮点数,最?的1位存储符号位S,接着的8位存储指数E,剩下的23位存储有效数字
?
对于M,由于其1=<M<2,所以其整数部分绝对默认都是1,从而可以在存入时省略1,只存入小数部分,到读取时再取出来,从而节省了一位有效数字,使其更加精确。
?由于E在实际情况上是可能为负的,而E的格式是为无符号整数,所以其内存符合无符号整数格式,所以需要加入一个中间数,四字节为127,八字节为1023.
?
?此时就是按照存的过程反着来,这里很简单,就不过多叙述了。
当E全为0时 有效数字的被忽略的整数部分不为1,而为0,且指数E为1-127(-126),所以为无限接近0的小数。(此时取的过程步骤不符合平常取的步骤)
当E全为1时,此时指数为128,所以此时其数字大小非常大。(其取的过程符合平常取的步骤)
#include <stdio.h>
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;
}
首先n和*pfloat开始时其数据都是整形,第一个打印不用说绝对是九
而第二个解析如下
虽然是pfloat是浮点型指针,但由于其指向四个字节,整形也是四个字节,所以*pfloat为整形
在之后其9.0因为是double类型,但为八个字节,而pfloat为4个字节,所以编译器就只能将其由double转换为float类型,使其刚好为四个字节被接受,从而pfloat变为float类型。?值太小所以变化时大小没影响。
当其第三个打印时,其内存表明为
第四个显而易见?,打印出9.000000(正常情况都是显示小数点后6位)。
打印结果如下?
?对于一个浮点数来说,存到内存上去要转换为二进制,而有些浮点数如0.5就能直接转换为二进制0.1,而对于有些浮点数来说是一直求不到完整的,如3.14,会一直求下去,而其小数内存是有限的,所以必须得把后面的给切割了,这就造成了会有精度损失。
从而在对浮点数进行比较时,如9.87就在计算机中不等于9.30加0.57,因为虽然我们写的是3.14,但因为其二进制一直求不出来,所以会导致其3.14在内存中的值并不是3.14,会有精度损失(大一点或小一点),9.30和0.57也同理 ,所以就不相等。
?这上面是一个精度损失等于两个精度损失的数之和,虽然假设没有精度损失情况下它们相等,但是精度损失的量数不同的所以不相等。?
而这个是左边一个精度损失,右边一个精度损失,所以在没精度损失的情况下本就相等,在双方精度损失的数都相等的情况下依然双方相等,所以跟上面的另一个不一样。?
这就是精度损失所造成的不同情况,所以在?浮点数进行比较时,尽量不要用==进行比较,很危险,一些你认为安全的代码在运行时会发生很多错误。
对于浮点数在内存中的存储来说,我们现在先只需要知道这些重点,必须要知道的就行了,不需要把它的所有细枝末节全部都搞完,那非常浪费时间,而且平心而论,浮点数作者讲的已经很细了,所以浮点数目前只需要了解以上知识点就行(对于浮点数在内存中的计算在之后的文章会讲到,现在还没到时候)
这就是数据在内存中的存储,分为整数和浮点数两大块,现在讲完了(之后可能还会再讲一些与其相关的知识点,但现在已把最重要的点都讲完)。
下一篇文章将会讲述结构体这一知识点,敬请期待!谢谢大家!