| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Java知识库 -> 【数据类型存储原理】数据的存储 - 深度剖析数据在内存中的存储 -> 正文阅读 |
|
[Java知识库]【数据类型存储原理】数据的存储 - 深度剖析数据在内存中的存储 |
数据的存储🌹前言
在了解数据如何存储之前,应该先了解我们常见的数据类型。 ?数据类型汇总在C99标准中,我们可将数据类型划分为以下几大类。
下面一一介绍这五种类型的基本情况。 🎁整型家族
注:在C99之后的标准规定,将char类型数据划分为整型家族,因为字符在内存中会将其转化为ASCII码值进行存储。
在C99中还引入了long long - 长长整型,用法和long类型一致,但C语言语法规定,sizeof(long)<= sizeof(long long),而long类型所占内存大小为4/8字节,所以long long类型所占内存空间大小一定为8个字节。 🙈浮点型家族
浮点型家族只有float和double这两种类型,float类型所占空间大小为4byte,double类型所占空间大小为8byte。
🦝自定义类型
这里可能会有很多人无法李姐为什么数组类型也被划分为自定义类型,这里稍微做一些解释。 我们知道数组类型的变量定义形式:数据类型+数组名+[数组大小]; 如:
这里可能会让很多人产生误区,认为arr数组的类型是int类型,也就把这条语句理解为是int类型的、数组名为arr的数组大小为10的数组,其实不然,这个数组的数组名确实是arr,但其数据类型是int [10],这里可能让大部分人无法接受, 举个简单的例子即可解释: 我们知道,sizeof操作符是用来计算所占内存空间大小的,其操作数既可以是变量名,也可以是变量类型。
这两种写法都正确,打印结果为: 而对于数组,操作数也同样可以是数组名或者数组类型:
其打印结果为:
知道了这点,解释为什么数组类型是自定义类型就更清晰了,用上面解释的结论就可以知道,int arr[10]和int arr[9]的数组类型不同,并不都是int类型的,数组大小是我们程序员人为规定的,所以可以把他划分为自定义类型。 其他的自定义类型比较明显,这里就不一一解释。 🐱?🏍指针类型。指针类型很特殊。 我们常说的指针有两个含义:
常见的指针类型有:
这里着重介绍一点,指针变量赋值大部分都是取出某变量地址存放进指针变量,如int pc = &c; 但有一个例外:
这里之间将一个字符串常量赋值给指针变量pc,我们知道,字符串常量时放在常量区的,他的值不可修改,并且这里的字符串加上隐藏的’\0’总共是12个字节,而我们的指针变量根据平台的不同只能是4/8个字节,怎么都不可能放的下这个字符串常量,所以这么理解是错误的。 我们将其打印看看结果:
🐥空类型
下面举几空类型的例子帮助理解:
这里test函数的返回类型就是void。
这个代码就是将函数的参数置为空,表示不允许主调函数传参,如果非要传参,编译器将给出警告。
表示定义一个指针pc,但他什么都不指向,作为一个空指针存在。 🕸大小端字节序说明
🧠出现大小端字节序的原因
🐉字节序的概念
上面的文字描述也许过于抽象,接下来用较为容易理解的方式分别简单的介绍大端字节序和小端字节序的概念。 ?大小端字节序所谓大小端字节序,就是将多字节数据中的高低字节位按不同顺序存放在内存中的高低地址处,相当于顺(逆)序存放。接下来博主将把上述抽象概念划分逐一介绍:
我们知道一个数据根据大小不同被划分为不同的数据类型,各数据类型所占字节数不同,我们也就据此根据数据字节大小来将其存放于不同的数据类型中。 比如字符类型 - 其扩展之后的ASCII码值为0~255,我们知道一个字节是8位,按照无符号字符型的理解也就是从00000000 ~ 11111111,刚好是0 ~ 255,所以字符类型被称为单字符类型数据。 而十六进制数,如:0x11223344则为多字节数据,其中有4个字节,分别是0x11、0x22、0x33、0x44,像这样的数据则被称为多字节数据。
在一个二进制序列中, 如:0101011010100101101010010110 我们把前方高亮部分的0101称为高字节位,把后端加删除线的 其实很好理解,因为最后一个1的的权重为20,也就是2的0次方,而第一个0的权重为231,也就是2的31次方,以此来区分高低字节位也是很不错的选择。 接下来介绍大小端字节序的存储方式: 大端字节序 所谓大端字节序,就是将处于高字节位的数据存放在内存的低地址处,将处于低字节位的数据存放在内存的高地址处 如今给一数据:0x11223344 在内存中的存放形式为:
小端字节序 所谓小端字节序,就是将处于高字节位的数据存放在内存的高地址处,将处于低字节位的数据存放在内存的低地址处 今给一数据:0x11223344 在内存中的存放形式为:
在博主使用的VS2019编译器上,采用的就是小端字节序: 例:
调试 - 内存窗口(&a):
也就是小端存储模式。 👩?🍳百度系统工程师笔试题(通过编程判断该编译器为大端存储还是小端存储)百度2015年系统工程师笔试题:
该题前半部分在上文其实已经解决了,这里博主将分析问题,并实现代码。 🧣问题分析要判断编译系统到底是大端存储还是小端存储,其实并不复杂。 如0x11223344 如果是在大端存储模式下: 如果是在小端存储模式下: 所以其实只需要知道第一个字节的内容到底是11还是44就可以判断了。 1的高字节位就是00,低字节位就是01,比较好判断。 🎒代码演示
运行结果: 🎮代码分析
这里如果有没有讲清楚的地方,欢迎评论区留言或者私信博主解决嗷。 🧶整型数据在内存中的存储数据在内存中的存储遵循一定的法则,而整型数据和浮点型数据在内存中所遵循的法则是不同的,这里我们先介绍整型数据在内存中是如何存储的。 介绍整型数据的存储需要先引进一个概念:原反补码。 💣原码、反码、补码
原码: 所谓原码,就是将数据直接翻译为二进制序列。 拿32位平台举例,最高位作为符号位,正数的符号位为0,负数的符号位为1,后面的31位称为有效位,以不同的权重计算出不同的数字,最低位的权重为20,其次为21,以此类推。 如:
反码: 反码,顾名思义,就是将原码的二进制序列按位取反,但这里需要注意,并不是将所有的二进制位都按位取反,符号位是特殊独立出来的,他表示一个数的正负,随意取反可能会遭遇意想不到的结果。 所以反码应该通过原码除符号位,其他位按位取反获得。 如:
补码: 整数在内存中的存储存的都是补码,所以要通过上面的反码求出补码,补码的获取规则是原码按位取反(除符号位)再加一。 如:
因为整数在内存中的存储形式是补码,所以引出原反补的意义就是求出补码,而补码的计算公式为:补码 = 原码按位取反(除符号位)再加一 这里我们通过VS2019编译器进行验证内存中存储的是数据的补码:
编译器下调试 - 内存 - &a:
为小端存储模式,00001101转换为十六进制就是0d。 编译器下调试 - 内存 - &b:
为小端存储模式,1111 1111转换为十六进制就是ff,1111 1101转换为十六进制就是fd。 如此说来,在内存中真的存放的就是补码,所以为了弄清楚整型数据在内存中的存储,必须牢牢掌握原反补的概念。 🔨截断与整型提升
接下来简单讲解截断和整型提升的原理。 截断 假设我有一个32位二进制序列: 这是一个非常大的数字: 有一个char类型的空间: 在把32位数字往里放的时候会发现放不下,便会发生截断,只保留低八位的数字,其他24位数字直接舍弃, 最终存放的结果为: 这就是截断的过程。 整型提升 当我要将char类型的数据以%d的形式打印时,我们知道,%d是打印有符号整型,打印的是32位0/1序列的最终结果,但我们的char类型里只存放了8位,这个时候就会发生整型提升。 整型提升规则:
如: 今有一8位无符号数。
首先我们写出该数的二进制序列。 10010100 - 148 由于变量a是无符号类型的,所以不管该二进制序列首元素是0还是1,都将全部补0 获得: 00000000000000000000000010010100 最终打印的结果就是148 🎉整型数据存储练习
首先VS2019编译器对char类型的处理为默认认为是有符号的char,所以变量a和变量b属于同一类型。 先计算出-1的补码。
三个变量都是char类型,所以存储时都将发生截断。
现在要将三个变量以%d形式打印,则会发生整型提升。
变量a和变量b整型提升后的结果为:
变量c整型提升后的结果为:
因为提升后的c符号位是0,所以原反补码均相等。 而按%d形式打印需要将补码转化为原码后转化为十进制进行打印, 所以:
这么一来,打印的结果就应该是-1 -1 255 打印结果:
这道题的变量a是有符号的char类型的。 首先计算出-128的原反补码。
将01111111111111111111111110000000这样一个二进制序列存放进a中将会发生截断。 截断之后a中存放的结果为:10000000 这时以%u的形式打印,也就是以无符号整型的形式打印,要进行整型提升,而变量a是一个有符号的char类型,第一个元素是1,所以整型提升24个1。
这时要将提升之后的补码转换为原码后以十进制的形式进行打印。 而%u的形式将把补码中的符号位看做是有效位,所以其原反补都是一样的。
而11111111111111111111111110000000的值应该是4,294,967,168 所以输出结果:
还是一样,先求出128的补码,由于128是正数,所以其原反补都是相同的为:
存放进变量a中将发生整型截断:
而变量a为有符号的char类型,所以整型提升为
变量a以%u形式打印,则把符号位看成有效位,则此时原码反码补码相同,直接进行计算,11111111111111111111111110000000的十进制形式为4,294,967,168 所以打印结果为:
还是先把-20和10的补码计算出来,但是这里的i和j都是整型变量,所以不会发生截断和整型提升。
数据的计算是按照二进制补码的形式进行计算的,最后的结果再根据打印要求或者存储要求进行调整更改。 计算的结果:
要求按%d的形式打印,则将计算的结果转化为原码以有符号十进制数打印。
计算结果为-10
程序分析: 变量i从9开始自减到0时,都可以正常进入程序打印的值就是
在打印完0之后,变量i再自减1,变成-1,按道理来说应该跳出循环,但我们注意,这里的变量i为无符号整型,而-1的补码为11111111111111111111111111111111,所以会被解析为一个特别大的正整数:4294967295。
这里博主随便截两张打印结果的图供大家参考。
程序分析: 根据代码可知数组中第一个存放的数应该是-1,第二个是-2,以此类推。 但是这个数组是char类型的,我们知道char类型可存放的数据范围是-128~127,所以这些数据一直自减到-128之后,如果再自减就会放不下了,但是这里有一个小知识点。 通过画图给大家讲解。
综合以上三点可知,char类型的补码其实是以从-1,-2,…,-127,-128,127,126,…,2,1这样的方式连续的。 画成图的形式为:
其实这又是一个无限循环的存放,一直存放满1000个数据为止。 而打印的是字符串长度,使用的是strlen函数,strlen函数遇到\0就停止计算,所以计算的结果应该为128 + 127 = 255。
程序分析: 首先定义了一个全局变量:无符号整型i。 无符号的char类型范围是0~255,所以代码前面会打印255个"hello world\n",这一点肯定没错。 而255作为无符号数在内存中的补码是:
自增1之后的结果是:
将这个数存放于变量i中必然是存不下,所以会发生截断。 只保留低八位存储,所以变量i现在存储的是00000000,也就是0,是一个无符号数,原反补相同,并且符合循环条件,所以循环又开始了。 经过上述分析,该代码的结果应该是一个无限打印的死循环。 🏆浮点型数据在内存中的存储首先我们先见一下常见的浮点型数据有哪些?
浮点型数据类型:
long double是在C99标准中引入的,比较老旧的编译器都不支持这种写法。 浮点数表示的范围:在"float.h"文件中可以查看。 在此文档中即可查看浮点型数据的范围大小。 接下来介绍浮点型数据在内存中的存储方式。 🎨证明整数和浮点数的存取方式不同浮点数存储的例子:
打印结果为:
以上例子证明了整型数据和浮点型数据的存储方式是截然不同的,接下来就 🍦IEEE标准形式根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示为下面的形式:
举两个例子:
二进制数011就是十进制数3,小数点后面的第一个1表示1.0 / 21,第二个1表示1.0 / 22
二进制数0就是十进制数0,小数点后面的1表示1.0 / 21。 ==注意:==小数点后面的数都是按照1.0 / 2n的形式相加得到的,所以很多数其实是得不到准确值的。 🍚IEEE存储标准规定
画图说明:
对于M(有效数字)的规定:
对于E(指数部分)的规定: 至于指数E,情况就比较复杂。
加上127或者1023进行存储的原因是取出该数的时候就需要减去127或者1023,这样E就可以得到负数的情况。 🥛IEEE读取标准规定指数E从内存中取出还可以再分成三种情况:
举个简单的例子,如十进制数-12.75,转换为二进制数为:-1100.11,转换为IEEE标准形式为(-1)1 ? 1.10011 ? 2 3,此时的S = -1, M = 1.10011,E = 3 以单精度浮点型为例,将其存入内存的方式为:把S放在第一位作为符号位,E加上127,即3 + 127 = 130转化为二进制数10000010,把M的整数部分去掉,将小数部分存储,E和M不够的位全部补0。 即
在VS2019编译器上测试:
调试 - 内存 - &f: 编译器的形式为十六进制 将其翻译为二进制为:
而我们刚才的计算结果是:
可以发现,和我们写的正号相反,这说明浮点型数据在内存中存储也遵循大小端字节序规则,且这里遵循的是小端字节序。 最后,我们在来看最开始给出的那道例题:
程序分析:
其二进制序列为:
第一次打印为整型打印,输出为9
第一部分为S(符号位),第二部分为E(指数位(需要减去127/1023)),第三部分为M(有效位(小数部分)) 符号位为0,说明是正数,指数位为全0,减去127后得到-127,放在指数部分是2-127,即1.0 / 2127,是一个非常小的数,无论M(有效位)为多少,这里都将翻译为0,所以第二次打印结果输出为0.0。
十进制数9.0,转换为二进制数为1001.0,转换为IEEE标准格式为(-1)0 ? 1.001 ? 23。 即
第三次打印结果为将这个二进制数翻译为十进制。 即1091567616? 所以,第三次打印结果为1091567616? 第四次打印为按浮点数打印,即按浮点数形式取出数据,所以第四次从打印结果为9.0。 打印结果: 🍃总结
最后我是Aaron,希望今天的博文对各位有帮助,别忘了三连支持哇~ 👍点赞👍 + 👀关注👀 + ?收藏? 如果以上内容有任何不懂的地方欢迎评论区留言或者私信博主哦~ |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 21:09:27- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |