目录
我们知道数据的基本类型分两大类 整型和浮点型,那么讲讲它们分别是如何在内存中存储的吧。
整型在内存中的存储
首先我们得知道有哪些整型:char、int、short (int)、long(int) 等
在之前呢博文中也谈到了整型在内存中存储的是二进制补码,为了便于展示,我们一般看到的是十六进制,而二进制又分成符号位与数值位两部分,符号位也就是二进制表示中的最高一位,0表示“正”,1表示“负”。而正数的原码、反码、补码都相同;负数的原码、反码、补码都不相同。如下:
原码:直接按照数值的正负数的形式翻译成二进制就是原码。
补码:将原码的符号位不变,其它位通通按位取反即可拿到补码。
反码:在补码的基础之上再加1。
int a = 10;
//00000000 00000000 00000000 00001010 原码
//00000000 00000000 00000000 00001010 反码
//00000000 00000000 00000000 00001010 补码
int b = -10;
//10000000 00000000 00000000 00001010 原码
//11111111 11111111 11111111 11110101 反码
//11111111 11111111 11111111 11110110 补码
为什么内存中存补码
我们先用编译结果说话,因为正数原反补一样,无法观测,那么就看负数:
b在内存中的数据是0xff ff ff f6 恰好与-10的补码对应起来了。
原因:我们使用数值的补码,可以将符号位和数值域统一处理;同时加减法也可以统一处理(CPU只有加法器),而且补码与原码的相互转换,运算过程是相同的,省去了额外的硬件电路。
简单来说:1. 计算机只能进行加法的计算,而数值的二进制同样可以进行加法计算,2. 在补码准换成原码和原码转换成补码的过程是一模一样的,及二进制补码取反+1同样也可以得到相应的原码 。
例如用二进制计算 1-1 及1+(-1)
//00000000 00000000 00000000 00000001 1的原、反、补码
与补码相加
//11111111 11111111 11111111 11111111 -1的补码
//00000000 00000000 00000000 00000000 相加结果
与原码相加
//10000000 00000000 00000000 00000001 -1的原码
//10000000 00000000 00000000 00000010 相加结果
与反码相加
//11111111 11111111 11111111 11111110 -1的反码
//11111111 11111111 11111111 11111111 相加结果
我们发现只有补码相加得到的才是正确结果。
练习(unsigned使用)
一:
#include<stdio.h>
int main()
{
char a = -1;
//11111111 11111111 11111111 11111111 -1的补码
//11111111 截断
//%d打印有符号整型
//11111111 11111111 11111111 11111111 整型提升
//10000000 00000000 00000000 00000001 求原码
signed char b = -1;
unsigned char c = -1;
//11111111 11111111 11111111 11111111 -1的补码
//11111111 截断
//%d打印有符号整型
//00000000 00000000 00000000 11111111 unsigned类型整型提升,直接补0
//高位符号位为0,即原码
printf("a=%d b=%d c=%d", a, b, c);
return 0;
}
二:
#include<stdio.h>
int main()
{
unsigned int i;//定义无符号整型变量i
for (i = 9; i >= 0; i--)
{
printf("%d\n", i);
}
return 0;
}
编译结果:循环无限执行下去。
理由:当 i= 0执行完了之后就 i-- 使 i 变成 -1,但是 i 是个无符号的整型,所以 i 就是二进制位为32个全1的极大数字,后面的执行也以此类推(unsigned int存的是非负整数)
三:
#include<stdio.h>
int main()
{
unsigned char i;//范围在 0~255之间
for (i = 0; i <= 255; i++)
{
printf("ha");
}
return 0;
}
与第二题类似,可参考。
四:
#include<stdio.h>
#include<string.h>
int main()
{
char a[1000];//定义字符数组
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;//数组的内容
}
printf("%d", strlen(a));//strlen计算到\0就停止
return 0;
}
分析:字符数组中依次存放的是 -1 -2 -3....-999 -1000,而strlen是找 \0(ASCLL对应的是0) 的,所以说我们要找0。上面的学习了解到char类型的取值范围是2^7~2^7-1(-128~127)之间。
看图更好理解 所以结果就是在128+127+1的位置上放0,故编译结果是否就是255(127+128)呢
整型的取值范围
所以说 signed char 类型的取值范围在 -128~127 之间。即 -2^7~2^7-1 之间
所以int 和short的类型也可以借此类推。
而对于无符号整形呢,无符号数无负数全为正数即二进制都表示的是数值部分
补充:在发生数据的整型提升时 ,取决于数据是无符号还是有符号类型
浮点型在内存中的存储
存储原理
根据 IEEE 754 标准规定任意一个二进制浮点数V可以表示成下列形式:
V =(-1)^S *M *2^E
(-1)^S表示符号位,S=0时表示V为正,S=1时表示V为负
M表示有效数字,大于等于一小于等于二(写成科学计数法的形式)
2^E表示指位数(E为无符号整数)
在了解小数存储之前我们得先了解二进制每一位的权重,我们已经谅解整数存储的二进制展示中每一位都有自己对应的二进制位的权重,从2^0开始的,这些都是大于等于1的数,那么大于0小于1的呢,同样是二进制位的权重,从小数点后面开始二进制位的权重从2^-1开始依次向后。
举例熟悉原理
存储小数5.5,从存储原理中一一对应起来
符号位即:(-1)^0 5.5为正数,即符号位是0
有效数字与指位数:5即:101 而0.5即:2^-1 合起来就是101.1,而有效值是有取值范围的,那么就表示成:1.011*2^2,所以存的是有效数字:1.011。指位数为2。所以S:0 M:1.011 E:2。
(这里2^2不理解的话可以参考十进制的科学计数法求法)
float类型与double类型的空间划分
浮点数在内存的存储
M的存储、提取方式
前文中也提到了M用科学计数法来表示的,并且小数点前面的一位总是1,因此在计算机存储时前面的1可以直接省略,存储小数点后面的数,所以我们将数据从内存中取出来的时候就要自动补上一个1。存储形式就是按照从左往右存储
我们呢给M留的空间是比较大的,但是不一定有这么多的数据去放慢M呀,那么没有放满的部分就全部用0补满。
E的存储方式
上面提到E为无符号整数,如果在 float 类型行中E的取值范围在 0~255 那么在 double 类型中呢,E就是 0~2047
但是就有一个问题了:像0.5这样的数E又是多少呢?
0.5用科学计数法表示是:1.0 * 2^-1,那么说E不就是负数了嘛,那可咋整丫?
IEEE 754规定,存入内存时E的真实值必须在加上一个中间数,对于8位的E,这个中间数就是127而对于11位的E来说,中间数就是1023.
所以回到问题中:0.5这个小数存在 float 中的E就是 -1+ 127=126
但是你这个机灵鬼是否又会想到如果是一个极小的数,真实值小于-127的呢?
那么就得说说浮点型的范围了,范围的值中不允许E的真实值这么小,所以最小也是-127
举例
假如像内存中存放 float 类型的浮点数5.5
从内存中提取浮点数
知道了怎么存放,就要了解一下该如何从内存中拿出浮点数。
E不为全1或不为全0
浮点数就采用指数E的计算值(内存中的值)减127(或1023),得到真实值,再将有效值M前面加上第一位1.
E全为0
浮点数的指数E的真实值就等于1-127(或1-1023),而有效值M就不再加上第一位的1,就直接是0.xxxxx 的小数了。那么这样拿出来的一个数就是无穷接近于0的一个浮点数了。
E全为1
此时如果有效数字M全为0的话,那么表示的就是可以存在浮点型中的一个极大的数或极小的数了(取决于S)
小题练一练
#include<stdio.h>
int main()
{
int n = 9;
float* p = (float*)&n;//强制类型转换成浮点型,但并不改变n的类型
printf("n的值为:%d\n", n);
printf("*p的值为:%f\n", *p);
*p = 9.0;
printf("n的值为:%d\n", n);
printf("*p的值为:%f\n", *p);
return 0;
}
结果:
注:格式符与数据类型一定要对应上。