数据的存储

31 篇文章 2 订阅

导语:

本章会对各类数据类型进行介绍,重点讲解整型浮点型在内存中是如何存储的。

思维导图:

1.数据类型介绍

C语言基本的内置类型及所占空间大小(32位机器为例):

char         //字符数据类型   1byte
short        //短整型        2byte
int          //整形          4byte
long         //长整型        4byte
long long    //更长的整形    8byte
float        //单精度浮点数  4byte
double       //双精度浮点数  8byte

那设置这些类型到底有什么意义呢?
其实使用这些数据类型是用来开辟内存空间的,比如说用char类型,就是向内存申请一个字节的空间;其次就是看待所开辟的内存大小,比如说同样是4个字节,用int类型申请的话,那么内存里就存放的是整型数据,用float的话,那么内存里就存放的是浮点型数据。
类型的意义:

1.使用这个类型开辟内存空间的大小(大小决定了使用范围)
2.看待内存空间的视角

1.1类型的基本归类

整型家族
在这里插入图片描述

在这里我们发现char字符类型也在整型家族里面,这是因为字符在存储的时候存储的是ASCII码值,ASCII码值是整型,所以在归类的时候将char类型归于整型家族。
另外,我们在写代码的时候,上图方括号里面的int以及signed,是可以省略的,编写时语法如下:

signed int a = 0;       //等价于 int a = 0;
signed long a = 0;      //等价于 long a = 0;
signed long long a = 0; //等价于 long long a = 0;

char类型是signed char 还在unsiged char,标准并没有规定,这个是取决于编译器的,但在常见的编译器上signed char == char,我们以VS2022为例(如下图)
在这里插入图片描述

浮点型家族
在这里插入图片描述

浮点型家族比较简单,就两个,稍后会讲解float和double的区别。
构造类型(自定义类型)
在这里插入图片描述

这些类型都是我们自己根据实际需求创造的类型,可以理解为上面几种类型的集合。比如说,一个学生我们无法根据一个整型或者一个浮点型去定义他,那么我们就可以自己创造一个学生类型,里面包含了整型、浮点型、字符类型,以结构体为例:

struct Stu
{
	int age;
	char name[20];
	float grade;
};

指针类型
在这里插入图片描述
空类型

void表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型

void test()
{
	//无返回类型
}

void test(void)
{
	//无返回类型,无参数
}

void* pv; //无类型的指针

2.整型在内存中的存储

变量的创建是要在内存开辟空间的,空间的大小根据不同的类型而决定。

数据在所开辟的内存中到底是如何存储的呢?

例子1:

int a = 5;
int b = -10;

通过编译器调试我们发现,这两个数据是这样存储的(如下图)在这里插入图片描述
之前在此片文章提到过(文章链接:C语言——操作符(上))整型在内存中存储的其实是二进制序列的补码,要了解存储,尤其是整型的存储,我们需了解三个码:

原码、反码、补码

2.1 原码、反码、补码

计算机中的整数有三种2进制表示方法,即原码反码补码
三种表示方法均有符号位数值位两部分。
符号位 :
0表示“正”,用1表示“负”

数值位
正数的原、反、补码都相同。
负整数的三种表示方法各不相同。

原码
直接将数值按照正负数的形式翻译成二进制就可以得到原码。

反码
将原码的符号位不变,其他位依次按位取反就可以得到反码。

补码
反码+1就得到补码。

我们还是通过例子1来验证在这里插入图片描述
通过验证,我们能确定整数在内存中存储的是它的补码,而且我们还能发现,在VS2022编译器这个环境中,是倒着存放的。
对于整型来说:数据存放内存中其实存放的是补码。
那为什么在存储中要存储补码呢?

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;
同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

通过例子2来感受一下

int main()
{
	int a = 1;
	int b = 1;
	int c = a - b;
	//1-1
	//只有加法器,所以会转换成 1+ (-1)
	//先用原码计算
	//00000000000000000000000000000001  -> 1的原码
	//10000000000000000000000000000001  -> -1的原码
	//10000000000000000000000000000010  -> 结果为-2
	//补码计算
	//00000000000000000000000000000001  -> 1的补码
	//11111111111111111111111111111111  -> -1的补码
	//100000000000000000000000000000000 ->通过计算,有33位,但整型只有32个比特位,存储不了这么多,就会截断
	//00000000000000000000000000000000  -> 结果为0
	return 0;
}

再一个,原码、补码可以相互转换:
在这里插入图片描述

所以用补码存储基于各方面都是十分方便的。
小结

整数在内存中存的是补码;
正整数的原、反、补码相同;
负整数的反码、补码要通过计算。

在这里插入图片描述
先前的例子,我们发现a和b虽然是按补码进行存储的,但是这个存储的顺序有点蹊跷。这又是为什么呢?

2.2 大小端介绍

什么是大小端
数据在内存中存储是以字节为单位来存储的,存放的时候顺序可以正序放,也可的倒序放,也可乱序放。但是在读取数据的时候,正序放进去正着拿出来,倒序放进去倒着拿出来,那乱序存放的话,拿到时候就比较麻烦了,所以最终数据在存储的时候,并没有采用乱序放在,而是采用了正序和逆序放置。
在这里插入图片描述

大端存储(大端字节序存储)
把一个数据的低位字节的内容存放在高地址处,高位字节的内容存放在低地址处

在这里插入图片描述

小端存储(小端字节序存储)
把一个数据的低位字节的内容存放在低地址处,高位字节的内容存放在高地址处

在这里插入图片描述
为什么有大端和小端

在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit。但是在C语言中除了8 bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。

我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

我们接下来通过写一个程序来判断,我们当前的机器的字节序

分析:
我们通过 1(0x00000001) 这个数字来判断
大端:00 00 00 01
小端:01 00 00 00
我们可以通过拿第一个字节的数据来判断,是00就是大端,01就是小端

int main()
{
	int a = 1;
	char* p = (char*)&a; //强制类型转换成char*,这也就能每次只访问一个字节的内容了
		if (*p == 1)
			printf("小端\n");
		else
			printf("大端\n");
	return 0;
}

输出:小端

通过调试判断能确定我们的代码的正确性,本机器的确采用的是小端字节序存储的。

在这里插入图片描述

3.浮点型在内存中的存储

浮点型在内存中的存储是和整型完全不同的,不可将整型的存储思路带到浮点型存储中来。
常见的浮点数

3.14159
1E9 (1.0*10⁹)
浮点数家族包括: float、double、long double 类型。
浮点数表示的范围:float.h中定义

可以通过float.h查看float和double类型的精度、取值范围,有兴趣可以看看,英文看不懂的话,就用翻译软件,大概了解一下即可。
在这里插入图片描述
了解浮点型的基本情况之后,那么浮点型究竟在内存中是如何存储的呢?先看下面一段代码

int main()
{
int n = 5;
float *pFloat = (float *)&n;
printf("n的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
*pFloat = 5.0;
printf("num的值为:%d\n",n);
printf("*pFloat的值为:%f\n",*pFloat);
return 0;
}

编译器输出的值:
在这里插入图片描述
为什么会是这样的输出呢?其实这就是因为整型与浮点型的存储形式不一样,整型的形式进行存储,那么浮点型去拿数据肯定是拿不到的,浮点型的形式进行存储,那么整型去拿数据也是拿不到的。

整型存储与浮点型存储是完全不一样的
整型的形式存储,只能整型的形式去拿数据
浮点型的形式存储,只能浮点型的形式去拿数据

3.1 浮点数存储规则

根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式:

(-1)S * M * 2E
(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。

我们以浮点数:V = 5.5为例在这里插入图片描述
任何的浮点数,都可以写成S、M、E这种形式,那我们在存储的时候就只需要把S、M、E这样的数字到内存中去,那么这个浮点数就相当于被存起来了,回头需要用的时候,只需拿出S、M、E还原成这个表达式,再还原成这个数字即可。

IEEE 754规定:
对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。
对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。

在这里插入图片描述
IEEE 754对有效数字M和指数E,还有一些特别规定
前面提到M的范围是[1,2),M只能写成1.xxxx(xxxx表示小数部分)

IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。

比如说保存1.011的时候,因为整数部分都是1,所以可以省去,不用保存,只需要保存小数位即可,即保存011,之后在读取的时候,再将第一位的1加上去。这样的话,就能多保存一位,精度就会更高一点。

以32位浮点数为例,留给M的只有23位,将第一位舍去之后,等于可以保存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。
这些是存储,在读取的时候,还需分为三种情况
E不全为0或不全为1

指数E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1
比如:
0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为1.0*2^(-1),其阶码为-1+127=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为:
0(S) 01111110(E) 00000000000000000000000(M)

E为全0
在这里已经是加了127存进去的,还是为0,这说明原先的值可能是-127甚至更小。

V=(-1)S * M * 2E
2(-127)这个值已经非常小了,还可能更小2(-130)、2(-140)
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字。

E为全1
同理,加上127为全1,说明这个数非常大232已经是42亿多了,如果再为2128以至于更大,那么这个数就能理解为无穷大了。

这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)。

了解了这些,我们就可以来验证,到底是不是这样存储的。

int main()
{
	float a = 5.5f;
	//101.1        --> 二进制
	//1.011 * 2^2  -->科学计数法
	//(-1)^0 * 1.011 * 2^2 
	//s = 0
	//m = 1.011
	//e = 2
	//s(1bit)    m+127(8bit)      e(23bit)b不足的补0
	//  0         10000001     01100000000000000000000000
	//01000000 10110000 00000000 00000000000  -->存储在内存中的形式
	//40 b0 00 00 -->16进制
	return 0;
}

在这里插入图片描述
通过验证,浮点数的确是按上面讲的内容来进行存储的,并且也是小端字节序存储。

结语:

本次内容主要讲解整型和浮点型在内存中的存储和读取,对于数据在内存中是怎样的有了更深的理解。内容稍微偏理论一点,这其实是在修炼内功,在今后就可以理解更深层次的内容,思路也会更加宽广。
那么本次的分享就到这里,如果对大家有帮助的话,不妨点赞评论收藏支持一下,再见!

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加法器+

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值