数据在内存中的存储方式

目录 

1.C语言数据类型介绍

2.整数的存储方式

3.浮点数的存储方式


一.C语言中的数据类型

  (1)整型家族(char , short ,int , long)

char
unsigned char
signed char
short
unsigned short [ int ]
signed short [ int ]
int
unsigned int
signed int
long
unsigned long [ int ]
signed long [ int ]
其中不同整型类型,能够代表整型的大小范围也不同,当然所占空间也不同,char占1个字节,short占2个字节,int占4个字节,long占4个字节(不确定,c语言标准规定sizeof(long)>=sizeof(int)即可)

 (2)浮点型家族(double , float)

float      单精度浮点型
double  双精度浮点型

 图来自菜鸟教程网站

 (3)构造类型

> 数组类型
> 结构体类型 struct
> 枚举类型 enum
> 联合类型 union

(4)指针类型(int* , void * , char * , float*)

(5)空类型(void)

1.函数返回为空 (比如一个简单的打印函数,就不需要返回值,不返回值的函数返回类型即为空)

2.函数参数为空,有些函数,不接受参数,则参数类型可以设置为空

3.指针类型为void *,该类型指针无法解引用,无法进行指针运算,但它可以根据我们的需求,转化为任意数据类型.

二.整数的存储方式

1.原码,反码,补码

我们知道减法实际可以看作加上一个负数,乘法可以看作不断相加,除法可以看作不断相减,因此,只要我们正确理解并处理好计算机中数据的加法,一切问题便可迎刃而解!

(1)补码出现的原因:
数字在计算机中是以二进制的方式进行存储,而在我们现实生活中,是有正数和负数的区别的,如何根据二进制来区分它是正数还是负数呢?

人们想到了一个简单的方法来区分正数或者负数,就是看最高位!

最高位为0,则为正数;最高位为1,则为负数

但是这样却会带来一个问题,一个正数与负数相加,并不符合我们的运算法则!

假设我们用int类型(32个bit位)来存储1,-1,假设电脑中只存在原码

    1
    00000000 00000000 00000000 00000001
    -1
    10000000 00000000 00000000 00000001

由于CPU中只存在加法器,如果按照原码直接进行相加,得到的二进制,转为数字,得到的是-2,显然1-1 \neq -2,得到的答案是错误的.

不仅仅如此,那0又该如何表示呢?我们在数字中可没有+0,-0的区分,这样就是单纯的浪费

于是聪明的科学家想了一种方法,也就是原码.反码.补码的方法

在解释什么是补码之前,我们先思考一个问题,为什么会出现原码相加,答案出错呢?

原因其实就在于我们的符号位代表的是什么?它是一个事物,是我们人为给它赋上相应的含义

而其余位则仅仅是数字

数学运算针对的仅仅是数字,而不是我们的事物!所以你说一个负数加上一个正数一定会是负数吗?肯定不是,但是负数最高位是1,正数最高位是0,两者相加得到的1,就意味着结果必定是负数,这显然是错误的!

所以出现答案出错的原因,本质就在于这是一种混合编码,我们将事物和数字混合在一起进行编码了,而这种编码肯定是不符合我们的运算法则的

那科学家是如何解决这个问题呢?

既然只有数字才能进行数学运算,那我们把最高位看作是数值不就行了?

这其实就是补码进行的操作,补码的最高位实际是带符号的数值!它的权重是最高的

对于1000这个二进制数来说,假如是无符号正整数,它就是8,但它同时也是-8的补码

Pro1.采用补码的形式,数字表示的范围是多少?

不难看出,当最高位取1时,表示的就是最大的负数,而当最高位取0时,表示的就是最大的正数(等比求和)

所以采用补码的形式,一个二进制数能表示的范围为

所以为什么有符号char类型,范围在-128到127之间?也很好解释了

Pro2.采用补码的形式后,为什么负数总会比正数多表示一个数呢?

一个二进制数能表示的总数必然是

但是不要忘记还要给我们的0留一个未知,所以正数始终比负数少一个,我们用一半的位置去表示负数

Pro3.具体如何从一个补码写出十进制整数呢?小数呢?

记住一个原则,补码的最高位实际是带符号的数值

小数也是同样的道理,小数也是有它对应的权重的

从这里也其实看出来补码的其中一个缺陷,并不好直接通过补码来对数字进行比较大小

Pro4.给出一个数字,如何迅速写出它的补码呢?

我们举一个小例子吧,比如5和-5这两个数字,采用补码的形式,则表示成这个样子

为什么-5的补码是这样表示?

我们从上述这个转换过程中能够有什么启发吗?

有人会说,为什么要写成这样的表现形式?

其实这一切都是为了凑出一个10来,通过10,将5和-5两个数字建立联系

我们可以发现,对一个正数二进制数字直接每一位取反,得到的其实就是该二进制数所能表示的最大数 - 该数字

比如说0101(5),每一位取反,得到的1010(10),两者相加必定全为1,即1111,对应该二进制数所能表示的最大数15,这很好理解

所以,任何一个负数的补码,它的变化规则如下:

符号位不变,其余位置按位取反(得到"互补正数"),再补上一个1

进一步思考,假如是一个负的小数呢?在哪里添上1?

假如理解上面的过程,就明白其实添1这个操作,本质是由于0霸占了正数的一个位置所导致的,我们要加上1,消去这个影响,使正数表示的范围(其余位)能够和负数(最高符号位)表示的范围相同

所以补1,始终是在末尾补1,加的不是"数值1"

更为详细的总结如下:

比如-1,这里拿一个字节存储举例

它的1原码是1000 0001,符号位不变,所有位按位取反,则它的反码是1111 1110

反码+1得到补码,也就是1111 1111,所以-1在计算机中就是1111 1111来存储的.

当然补码得到原码的另一种方式,除了逆着进行外,还可以重新按照原码得到补码的方式再重新操作一遍,非常神奇!!!

通过这种方法,我们不仅实现了符号位和数值域进行统一处理,无论负数还是正数,都有唯一一个二进制与之对应转化,同时,我们通过补码,统一了加法和减法运算.

 Pro5.假如我现在想把Pro4中的5从5位变成8位呢?又应该如何表示?

+5的表示直接往前填0,凑齐8位即可,

那-5呢?

由于变成了8位,则此时符号位所对应的权重,已经不是当初的那个它,变得更负了,当初只是,现在则变成,因此,假如我们想要它继续保持-5,不难看出,正数部分也要相应增大,消去对应权值才行,所以补1才是正确操作

由上述的补码运算规则,也很容易验证我们的结果是对的

正负数相加,就是对应补码进行相加即可,至于最后是负数还是正数,就看对应符号位与最高位数字位进位相加(舍弃进位)的结果.

2.大小端存储

 在了解正数以补码的形式存储后,我们就可以打开vs2019进行内存监视了,16的十六进制为10,在内存监视中,我们可以很好的观察到,不过为什么有点像是倒着存的?

数据的存储有如图两种存储方式, 统称为字节序存储(以字节为一个单位),像char类型一个字节大小,则完全没有大端小端这样的概念.

一种称为大端(字节序, 指数据的低位存储在高地址,数据的高位存储在内存的低地址

一种称为小端  (字节序),指数据的低位存储在低地址,数据的高位存储在内存的高地址

因此由上图在内存中的显示,我们可以知道,在目前X86环境底下是以小端字节序存储.而在我们知道的KEIL C51是大端字节序存储,有些ARM处理器,其可以通过硬件,根据自己的设置,调整是大端还是小端.

至于如何判断是大端还是小端,我们可以简单设计一个代码,1在大小端存储是不同的,我们取出1的地址(低地址),将其转为char *类型,再解引用(访问一个字节),得到1即为小端,得到0即为大端.

int check_sys(int x)
{
	return (*(char*)&x);
}
#include <stdio.h>
int main()
{
	int a = 1;
	if (check_sys(a))
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

三.浮点数的存储

1.浮点数的二进制转化

根据国际标准IEEE,任意一个二进制浮点数都可以表示为这样的形式(类似十进制的科学计数法)

  (-1)^S * M * 2^E

其中S用来确定正负,正数为0,负数为1,M是具体的一个数,范围在1~2间,E代表指数位

比如5.5,用二进制表示101.1,然后类似科学计数法,101.1可以表示成1.011*(2^2),然后是正数,所以5.5即可表示为(-1)^0 * 1.011 * 2 ^ 2,S为0(正数),M为1.011,E为2

但是我们也可以发现一些像是无限不循环小数,比如pi,只能无限逼近,而不能精确表示出来,所以我们也说C语言中,不能直接进行浮点数比较,原因就在此,本身存储就不是精确的.

2.存储方式

无论是单精度(float)或者是双精度 (double),其在内存中都是按照这样的格式进行存储,唯一不同的是float是32个比特位,而double是64个比特位

bit位数目 SEM

float

1823
double11152

对于M,由于M的取值范围必定在1~2之间,所以可以省略前面的1,例如1.011就可以省略1,只存入011进去,这样就可以多保存一位,达到24位

对于E,E是一个unsigned int类型的数字,但我们指数是可能出现负数的,所以为了让负数变为正数(无符号整型),像float类型,我们便加一个127(8个bit位能表示的最大负数)再存进去,对于double类型,则加一个1023再存进去.

Lg.例如0.5  二进制标准形式为(-1)^0 * 1.0 * 2 ^ (-1)   (1) -1+127 = 126 (2)1.0的1可以去掉

其在内存中存储即为0 01111110 00000000000000000000000

当E全为0,它原本的指数即为-127(1000 0001 + 0111 1111),代表一个非常接近0的数字;

当E全为1,它原本的指数即为128(1000 0000 + 0111 1111), 并且有效数字也全为0,则代表无穷大,由S区分,正无穷还是负无穷.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值