文章目录
前言
数据在内存的存储方式可以帮助我们更加深刻的了解数据在内存的分布情况,对我们C语言的学习有着极大的帮助。我们重点了解一下数据的储存。
一、数据类型
1.整形数据类型
char //字符数据类型
short //短整型
int //整形
long //长整形
long long //长长整形
2.浮点数据类型
float //单精度浮点数
double //双精度浮点数
3.构造数据类型
1数组类型。
2.结构体类型(struct)。
3.枚举类型(enum)。
4.联合类型(union)。
4.指针类型
int* p1;
char* p2;
float* p3;
5.空类型
void表示空类型(无类型),通常应用于函数的返回,函数的参数或者指针类型。
6.类型的意义
类型的意义:
1.使用这个类型所开辟内存空间的大小。
2.如何看待内存的储存空间的视角。
例如:
int main()
{
char n1 = 10;//一个字节
int n2 = 10;//四个字节
float n3 = 10.0;//四个字节
return 0;
}
我们可以看到整形和浮点数的储存不一样。这也证明了类型决定了我们看待内存的方式。
二、整形类型的存储
整形有以下几种类型:
char
signed char
unsigned char
short
signed short
unsigned short
int
signed int
unsigned int
long
signed long
unsigned long
其中signed代表有符号,unsigned代表无符号。
在计算机中的有符号数有三种表示方法,即原码,反码,补码(无符号的原码,反码,补码相同)。三种方法均有符号位和数值位两部分,符号位为0表示正,1表示负。而数值位表示方法个不相同。
原码:
直接将二进制按照正负数的形式翻译成二进制。
反码:
将原码的符号位不变,其他位按位取反。
补码:
将反码加1的到。
对于正数来说,正数的原码,反码,补码相同,对于整形来说,数据存放在内存的是补码。
为什么要存放补码:
因为在计算机系统中,使用补码可以将符号位和数值域的统一处理,同时加法和减法可以统一处理(CPU只有加法器)。并且补码和原码进行互相转化时,运算过程相同,不需要额外的硬件电路。
int main()
{
char n1 = -1;
//原码:1000 0000 0000 0000 0000 0000 0000 0001
//反码:1111 1111 1111 1111 1111 1111 1111 1111
//补码:1111 1111 1111 1111 1111 1111 1111 1111
//char类型只能存储1个字节,8个比特位,发生截断,
//n1的补码:1111 1111
signed char n2 = -1;
//n2的补码:1111 1111
unsigned char n3 = -1;
//无符号的原码,反码,补码相同
//n3的补码:1111 1111
//n3的原码:1111 1111
printf("%d %d %d\n", n1, n2, n3);
return 0;
}
三、大小端字节序
1.什么是大小端
大端(存储)模式:是指数据的低位保存在内存高地址处,而数据的高位保存在低地址处。
小端(存储)模式:是指数据的高位保存在内存高地址处,而数据的低位保存在低地址处。
2.为什么有大小端
在计算机系统中,我们是以字节为单位,每个地址单元都对应着一个字节,一个字节为8bit,在C语言中,除了有8bit的char型之外,还有16bit的short型等。另外对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器的宽度大于一个字节,那么就会存在多个字节的排序。因此导致了大小端存储模式。
3.判断大小端
在判断大小端之前我们先看大小端的具体存储情况:
下面我们通过代码和内存来看如何判断大小端:
int main()
{
int a = 1;
//0000 0000 0000 0000 0000 0000 0000 0001
//换成16进制为:00 00 00 01
//小端为01 00 00 00
//大端为00 00 00 01
char* p = (char*)&a;
printf("%d ", *p);
return 0;
}
union maxormin
{
char a;
int b;
};
int main()
{
union maxormin mm;
mm.b = 1;
printf("%d ", mm.a);
return 0;
}
上面两种方法,一种是通过强制转换指针判断,另一个是通过联合体判断,思路都是只取第一个字节的数据来判断是大小端的哪一个。
四、浮点类型的存储
我们先通过一段代码来看整形和浮点型存储的差异:
int main()
{
int n1 = 10;
float* pn1 = (float*)&n1;
float n2 = 10.0;
int* pn2 = (int*)&n2;
printf("n1以整形打印:%d\n", n1);
printf("n1以浮点形打印:%f\n", *pn1);
printf("n2以整形打印:%d\n", *pn2);
printf("n2以整形打印:%f\n", n2);
return 0;
}
n1和n2在内存中的存储情况如下:
我们先了解浮点数的存储方式,在根据上述程序进行解释。
在国际标准IEEE(电气和电子工程协会)754中,任意一个二进制浮点数可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^ S(表示符号位) ,当s=0,这个浮点数为正数,当S=1时,这个浮点数为负数。M表示有效数字,大于等于1,小于2 。2^E表示指数位。
IEEE 754中对于32位的浮点数(单精度),最高的一位是符号位S,接着的8位是指数E,剩下的23位为有效数字M。如图所示:
对于64位的浮点数(双精度)来说,最高的一位是符号位S,接着的11位是指数E,剩下的52位为有效数字M。如图所示:
IEEE 754中对有效数字M和指数E,还有一些特别规定:M大于等于1,小于2 。所以M可以写为1.xxxxx的形式,其中xxxxx为小数部分。
指数E的情况有几种:首先E为一个无符号整数,这意味着如果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分为3种情况:
1.E为全0时:这时的浮点数的指数等于1-127(或者1-1023),有效数字M不在加上第一位的1,而是还原为0.xxxxx的小数,这样做是为了表示接近于0的很小的数字。
2.E为全1时:这时如果有效数字M全为0,则表示为±无穷大。
3.E不全为1或不全为0时:采用上面的规则,即指数E的计算值减去127(或者1023)得到真实值,在将有效数字M前加上第一位的1。
int main()
{
float f = 9.0;
//原:1001.0
//1.001*2^3
//(-1)^0*1.001*2^3
//0100 0001 0001 0000 0000 0000 0000 0000
//16进制为:41 10 00 00
return 0;
}
数据的储存在内存中如图所示:
总结
数据的存储可以反应内存读的字节数,我们要深刻的了解数据在内存的分布情况。只有对内存了解并熟练掌握才可以让我们找到程序中我们可能在内存中存在的错误。