> 文档中心 > 数据的存储——C语言

数据的存储——C语言


目录

前言

一、数据类型的介绍

1.整形家族 

2.浮点型家族

3.构造类型

4.指针类型

5.空类型

二、整形在内存中的存储

1.原码、反码、补码

2.大小端存储

三、浮点型在内存中的存储

         总结


前言

C语言里面数据是怎么存储的,为什么要学数据的存储,数据的存储学了有什么用?在我眼里,数据的存储学了虽然不能提高你的写代码能力,但是学习数据的存储是能提高一个人的内功的,学习数据存储就是学习C语言内在的东西,学习了这些东西能够帮助你更好的理解每行代码是怎么运行的,让你在今后的学习中更好的理解C语言中的语法,所以学习数据的存储还是很有必要的。下面就由我来带大家学习C语言中数据的存储。 

一、数据类型的介绍

1.整形家族

char //字符型,大小为1byte,有人一定会问char明明是存储字符的,它为什么会在整形家族里面,因为char类型里面存储的字符是先转换成ASCII值,然后将ASCII值存储在char里面,而ASCII值是整形,所以char类型是整形家族的。下面是ASCII表,大家可以参考查阅。

unsigned char //无符号字符型,大小为1byty

signed char //有符号字符型,大小为1byty

等会儿下面会介绍有符号和无符号的区别,这里先别急。

short //短整型,大小为2byte

unsigned short [int] //无符号/短整型,大小为2byte

signed short [int] //有符号短整型,大小为2byte

int //整形,大小为4byte

unsigned int //无符号整形,大小为4byte

signed int //有符号整形,大小为4byte

long //长整形,大小为4byte(64位平台下为8byte)

unsigned long [int] //无符号长整形,大小为4byte(64位平台下为8byte)

signed long [int] //有符号长整形,大小为4byte(64位平台下为8byte)

long long  //更长的整形,大小为8byte(64位平台下还是为8byte)

2.浮点型家族

float //浮点型,大小为4byte

double //双精度浮点型,大小为8byte

3.构造类型

数组类型

结构体类型 struct

枚举类型 enum 

联合类型 union

4.指针类型

int *pi

char *pc

float* pf

void* pv

5.空类型

void 表示空类型(无类型)

通常应用于函数的返回类型、函数的参数、指针类型

二、整形在内存中的存储

1.原码、反码、补码

下面我们先介绍一下有符号和无符号的区别。

一般来说,short型int型都是指的signed shortsigned int,所以我们平时为了简便都是直接把signed省略了的,但是如果要表达无符号的类型就必须在类型前面加上unsigned,例如unsigned int(无符号整形),而有符号整形(int)最前面的数字(也就是最高位)代表符号位,1代表负数,0代表正数。而无符号整形(unsigned int)就没有符号位。

而char类型因为每个编译器不同,导致char类型可能是signed char也可能是unsigned char。

整形在内存里面都是以补码来存储的下面由我来介绍原码、反码、补码直接的关系。

因为整形大小为4byte也就是32bit,所以整形需要用32个二进制数来存储。

原码:原码就是将数字本身转换成二进制后的结果。最前面的第一位数字代表符号位,如果是正数的话第一位数字就是0,如果是负数的话第一位数字就是1。

反码:如果该整形数字是正数,那么原码、反码、补码都相等,如果该整形数字是负数,那么反码就是原码的符号位数字不变其余数字按位取反。

补码:如果该整形数字是正数,那么原码、反码、补码都相等,如果该整形数字是负数,那么补码就是反码加1。

那么为什么要用补码来存储整形呢?

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

下面就是正数和负数的原码、反码、补码的关系转换

 在内存中我们得到的是数字的补码,我们要想知道储存的是哪个具体的数字,我们需要将补码又转换成原码才能知道存储的是哪个数字,下面是补码转换成原码的两种方式。

 下面介绍几个类型存储数据的范围:

char型/signed char型:存储数据的范围为  -128~127

unsigned char型:存储数据的范围为 0~255

short int型/signed short int型:存储数据的范围为 -32768~32767

unsigned short int型:存储数据的范围为 0~65535

int型/signed  int型:存储数据的范围为 -2147483648~2147483647

unsigned int型:存储数据的范围为0~4294967295

下面我将详细讲解一下char型和unsigned char型存储数据的范围是怎么来的,后面其他类型都可以以此类推

而后面的有符号整形和无符号整形也是一样的计算了,这里我就不一一讲解了 

2.大小端存储

我们上面了解了整形家族是以补码的方式存储的,并且知道这些整形家族的数据存储的范围是多少。那么这个大小端存储又是什么东西呢?

通俗来讲,大小端存储就是将整形数据的补码存储在内存里面的一种规则,必须按照这个规则来将补码存储在内存里面,下面我们就用一个正数和一个负数来观察一下它们在内存中是怎么存储的吧

 看到这里你可能会问,你刚刚不是说整形是用补码来存储的吗,为什么这里面全是f6 ff ff ff这些,还有14 00 00 00 是怎么来的。原来计算机是把补码的二进制换成了16进制来存储的。

这时你可能又会问,为什么你的16进制补码好像跟内存里面存储的16进制补码有点不一样啊 ,好像是两个16进制数为一组然后颠倒过来存储的,这是因为两个16进制数占1byte,4组刚好为1个整形的大小(4byte),而为什么要颠倒过来存储就涉及我们的大小端存储。

下面我们来介绍大小端存储

而我们现在用的大多数编译器都是用的小端存储,这就是为什么我们看到的a和b数据的存储是颠倒的 。

三、浮点型在内存中的存储

刚刚说完了整形,现在我们来了解一下浮点型。

浮点型不同于整形,浮点型的存储不是用原码、反码、补码来存储的,而是以另外一种形式来存储的。

在我们讲浮点型存储之前我们先来做一道题,希望大家先做完再看答案

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;}

答案

 大家看完答案是不是有点出乎意外,这就是因为整形和浮点型的存储方式不同,下面就由我来带大家学习浮点型的存储方式。

虽然浮点型不是用补码来存储数据的,但是却比整形的存储方式要复杂很多。

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

表达式:(-1)^S * M * 2^E

1.(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。

2.M表示有效数字,大于等于1,小于2。

3.2^E表示指数位。

现在就由我来介绍这三个部分

首先我们还是要将一个浮点型转化为二进制,那就拿9.0和-9.0来举例子吧。

IEEE 754规定:

对于32位的浮点数,最高的1位是符号位s,接着的8位是指数E,剩下的23位为有效数字M。

(加起来就是32bit = 4byte也就是浮点型的大小)

 

 在这里直接将刚刚得到的S的数据填进去就可以了。

而IEEE 754对有效数字M和指数E,还有一些特别规定。

对于M

前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。 IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的 xxxxxx部分。比如保存1.01的时 候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位 浮点数为例,留给M只有23位, 将第一位的1舍去以后,等于可以保存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。

这样我们就可以开始存储了,还是以9.0为例:刚刚上面已经求到了S = 0,M = 1.001,E = 3

S是直接存进去,也就是0;而E的存储就要加上127再存储,也就是127 + 3 = 130,转化成二进制就是1000 0010,而M要先去掉整数部分的1,只存0.001,也就是存00100000000000000000000

将S、E、M结合起来就得到浮点型9.0存储的数据就是0 10000010 00100000000000000000000.

然后,指数E从内存中取出还可以再分成三种情况:

1.E不全为0或不全为1

这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将 有效数字M前加上第一位的1。

2.E全为0

这时,浮点数的指数E等于1-127(或者1-1023)即为真实值, 有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于 0的很小的数字。

3.E全为1

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

对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。

(加起来就是64bit = 8byte也就是双精度浮点型的大小)

 而双精度浮点型的存储和单精度的存储都是一样的,只是位数比较多而已,这个大家如果有兴趣可以自己去推推看。

最后我们在回来讲讲刚才上面那道题,相信很多人看到这里已经知道是怎么回事了,下面我们就来重新看看那道题。

 问题1.为什么 整形0x00000009 还原成浮点数,就成了 0.000000 ?

首先,将 0x00000009 拆分,得到第一位符号位s=0,后面8位的指数 E=00000000 ,最后23位的有效数 字M=00000000000000000001001

结合起来用浮点型的数据存储方式来看的话存储的就是

0 00000000 00000000000000000001001

由于指数E全为0,所以符合上一节的第二种情况。因此,浮点数V就写成:

V=(-1)^0 × 0.00000000000000000001001×2^(-126)=1.001×2^(-146)、

显然,V是一个很小的接近于0的正数,所以用十进制小数表示就是0.000000。

问题2.为什么num的值打印出来是这么大一个数

首先9.0是一个浮点型,所以9.0是以浮点型的存储方式存储的,

而上面我们已经知道了9.0存储在内存中的数据是0 10000010 00100000000000000000000

而我们打印的num是以整形打印的,计算机把我们存储的0 10000010 00100000000000000000000当成了一个整形,所以打印出来的数很大很大,及1091567616。

讲到了这里本章的内容已经全部讲完,希望大家都能理解数据的存储


总结

总的来说数据的存储就像程序员的内功,所以需要大家好好学习。才开始发博客,如果有什么地方写得不好请大家多多见谅,我会好好改进的,后面我会将自己的博客越写越好的,希望大家能多多支持,感谢大家。最后祝考研的同学都能上岸,找工作的同学都能进大厂。