前言:
有了上边一篇的预备知识,我们可以继续往下学习了。
C语言---最全二进制与进制转化大总结(数据在内存中的存储前传)-CSDN博客
原码、反码、补码:
整数的二进制的表示形式有三种,就是原码、反码、补码。
整数又分为有符号整数和无符号整数。有符号整数里边又分为正数和负数。其中,正数和无符号整数的二进制的原码反码补码相同。但负数的三种码都不同。
那么什么叫原码呢?
原码就是整数的二进制的表示形式,就比如5(int类型)这个数字的原码就是
00000000 00000000 00000000 00000101 (int类型是四个字节,就是32个bit)
又因为5是正数,那么它的原码反码补码都相同。同理,有符号整数也一样。
负数的情况
首先要先知道如果是有符号整数(包括正数和负数)的话,那么它的二进制序列的最高位被当作符号位,0表示正数,1表示负数。而无符号整数的最高位不代表符号位,被当作的是数值位。
1.负数的反码:
反码就是原码的符号位不变,其他位按位取反得到。
比如 -1 (int类型)
它的原码是 10000000 00000000 00000000 00000001
反码为 11111111 11111111 11111111 11111110
2.负数的补码:
补码就是反码+1就可得到
-1的补码就是 11111111 11111111 11111111 11111111
我们常常说计算机里边存放的是二进制序列,那么对于整数来说,计算机内存里边存放的其实是它的补码。
最后补充一个点就是补码除了可以通过-1,然后再取反的方式去得到它对应的原码之外,还可以直接取反+1得到。
大小端字节序和字节序判断
整数在存储的时候通常是以字节为单位的
比如现在有一个十六进制的数字 0x11002233
它是int类型,有四个字节,那么这四个字节在内存里边应该如何存储呢?
理论上来说,存放的可能性有无数种
比如 11 00 22 33,33 22 00 11,00 22 11 33...........
但是为了方便拿出使用,通常就只有前两种存放的可能,即为
11 00 22 33 和 33 22 00 11
以上这两种方式分别被称为大端存储和小端存储
下边画图帮助大家再次理解
注意:上图里边的那条线就指代内存,左边为低地址,右边为高地址。
下面来看一看在VS里边,数据是以什么方式存储的。
当我取出了a的地址,就会发现在VS里边是以小端的方式存储的。
知道了大小端存储方式的概念之后,那我们是否可以编写一个程序来判断一下你自己当前所用的环境是以大端形式存储的,还是以小段形式存储的呢?
思路:
既然是只要判断,那我们就没有必要用特别复杂的数据,其实,我们就用1来判断就可以了。
1的补码的十六进制表示形式是 0x00000001
我们可以取出它的地址,然后只获取里边一位字节的数据,再解引用,如果是1,就说明它是以小端的形式存储的,反之就是以大端的形式存储的。
代码实现:
#include<stdio.h>
int check()
{
int num = 1;
char* p = (char*)#
if (*p == 1)
return 1;
else
return 0;
}
int main()
{
if (check() == 1)
printf("小端存储");
else
printf("大端存储");
return 0;
}
浮点数在内存中的存储
浮点数存的方式
跟整数的存储不一样,浮点数在内存中的存储是有一套规则的。
根据国际标准IEEE754规定,任何一个二进制的浮点数V可以表示为如下的形式。
V = (-1)^S * M * 2^E
其中,(-1)^S 表示的是符号位,当S = 1的时候,V为负数,S为0的时候,V为正数。
M表示有效数字,是大于1小于2的
2^E表示的是指数位。
//拿十进制数字 5.0 来举例
先把他写成二进制的形式为 ->> 101.0
注意:之前说过二进制转化为十进制的时候每一位是有权重的,而在小数点后边也有权重,从 -1开始,依次往下。
而101.0用科学计数法表示就是 1.010 * 2^2,因为是二进制,所以底数是2。因此E为2,M为1.010,又因为5是正数,所以S就为0。
5.0用IEEE754标准来表示就变成了 (-1)^0 * 1.01 * 2^2
对于浮点数来说,它可以是float类型,也可以是double类型。请看下图。
对于如何存储E和M,还有一些其他的规定,我们要将M写成1.xxxxxx的形式,由于M是肯定大于等于1小于2的,所以在存M的时候就可以省略小数点前的1,就存储小数点后边的内容,这样做在一定程度上可以更加高效的利用空间,例如float类型留了23bit用于存放M,小数点前的1不存就相当于可以存24个有效数字了。
而对于E来说,E是无符号整数,但我们常常会发现事实是,当我们在科学计数的时候,E常常为负数,所以根据标准,如果E为8位,其取值范围就是0~255,如果为11位,取值范围就是0~2047,为了避免E是负数的情况IEEE 754就规定了在存储E的时候要加上一个中间数,对于float类型,中间数就为127,对于double类型来说,中间数就为1023。
浮点数取的方式
这里我们只考虑一般情况,E不全为0或1
其实浮点数在取的时候对于S来说,该是多少就是多少,对于M来说,取出来后还要在前边加上1,对于E来说,就要减去中间数。下面看一道题目。
题目代码:
#include <stdio.h>
int main()
{
int n = 9;
float* pFloat = (float*)&n;
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);
*pFloat = 9.0;
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);
return 0;
}
分析:
首先n是一个整数,它在内存里边存的补码为 00000000 00000000 00000000 00001001
紧接着取出了n的地址并把它转化为float*类型,也就是说此时程序会认为指针pFloat指向了一个float类型的数据,当我们用%d打印的时候,顺理成章,n本来就为整数,就应该这么打印,所以结果就为9。而解引用pFloat指针并且用%f打印的时候,编译器会认为n是一个浮点数,所以要想知道打印结果,我们就必须要用浮点数取的方式。
它的补码为 00000000 00000000 00000000 00001001
把他当成浮点数,那么S就为0,E就为 -127,剩下的是0000000 00000000 00001001,
值得注意的是,当E存在里边全为0的时候,M在取的时候小数点前边补的是0。所以M就为0.0000000 00000000 00001001。
最终拿到的结果 V = (-1)^0 * 0.0000000 00000000 00001001 * 2^-127
最后用%f打印的时候就为 0.000000
接下来通过解引用将n的值改为了9.0,然后用%d的形式打印,首先先将9.0写成二进制的形式,就为 1001.0,S就为0,M就为1.001,E就为3,存的时候加上中间数127就为130,130的二进制序列为 10000010 最终9.0的存储结果为 0 10000010 00100000000000000000000,当用%d打印的时候,编译器会把它当成整数,又由于符号位为0,表示正数,原码反码补码相同,所以最后是一个很大的正数。当用%f打印的时候也是顺理成章,结果就为9.000000