前言
对于数据在内存中的存储,在上一篇博客《C语言中的库函数》中有提到过一部分。这篇博客将会更详细的介绍,除了整数之外的浮点数存储形式。浮点数的储存真的复杂许多,对于新手来讲着实不太友好,再加上计算机是二进制存储,所以想要更细致的判断从存储格式到实际显示格式,对于无法整确被2进制解析的将会非常困难,比如说3.14。只能说设计者实在是太狠了,一般人一眼看不出来。
一. 整数
1. 整数的储存方式
在计算机中,整数以二进制在数据内存中储存。其中,最高位是符号位,0为正,1为负。数据以补码的形式储存,对于正数来说,原码就是补码。对于负数来说需要先转换为反码,再转换为补码。实际变换规则如下:
int a = -1;
//原码:10000000 00000000 00000000 00000001
//符号位不变,其他位取反
//反码:11111111 11111111 11111111 11111110
//+1
//补码:11111111 11111111 11111111 11111111
//如果想得到原码
//补码:11111111 11111111 11111111 11111111
//符号位不变,其他位取反
//10000000 00000000 00000000 00000000
//+1
//原码:10000000 00000000 00000000 00000001
看了这段代码不经有一种疑惑,为什么在的到原码的时候不将补码-1得到反码,再取反得到原码呢?这当然是可以的,只是这样子取反码的过程不是必要的,只记得取反、+1一种顺序也会有相同的效果。
2. 数据的显示
在调试窗口下,数据会以16进制的方式显示。例如:
int a = 0x11223344;
它的储存方式为44 33 22 11。
3. 储存顺序
数据在电脑中储存的方式有大端存储和小端存储两种形式,它们分别为:
(1) 大端存储
大端存储就是低地址存高位数据,高地址存低位数据。
//例如
int a = 0x11223344;
// a
// | <-表示a地址
// 11 22 33 44
//低地址---------------------------------高地址
(2) 小端存储
小端存储就是低地址存低存位数据,高地址高位数据。
//例如
int a = 0x12345678;
// a
// | <-表示a地址
// 78 56 34 12
//低地址---------------------------------高地址
那么数据存储就不能不按照这样的顺序存吗?按照其他的顺序大概是可以的,不过这种机器肯定是很稀有的,乱序不是那么好读取,所以这两种方式是比较主流的。
(3) 判断数据存储的方式
通过一段简单的代码就能判断你的电脑里是什么方式储存数据的。
#include <stdio.h>
int main()
{
int a = 1;
char* p = (char*)&a;
if(*p == 1)
{
printf("小端存储\n");
}
else
{
printf("大端存储\n");
}
return 0;
}
利用的原理就是用char*接收a的地址,如果是大端难么*p==0,反之*p==1。
我使用的vscode输出的是小端存储:
二. 浮点数
1. 浮点数的表现形式
浮点数主要有两种写法,一种是小数形式,例如:3.14。另一种是指数形式,例如:2E10,它的意思是2*10^10.
2. 浮点数的储存方式
浮点数和整数的储存方式大有不同,下面将给出一个例子展示给大家。
#include <stdio.h>
int main()
{
int n = 9;
float *f = (float*)&n;
printf("整数形式打印n:%d\n", n);
printf("浮点数形式打印n:%f\n", n);
*f = 9.0f;
printf("整数形式打印n:%d\n", n);
printf("浮点数形式打印n:%f\n", n);
return 0;
}
运行结果如下:
通过以上例子大概就能明白,浮点数和整数的存储是有很大不同的,特别是在吧9.0f存到n里面打印整数的时候得到了1091567616的答案。
通过最新的标准,浮点数的存储规则如下:
V = (-1)^S * M * 2^E;
其中S表示为符号位,M表示有效数字,E表示指数位。
(1) float
对于32为的浮点型来说,S占1个字节,E占8个字节,M占23个字节。
//例如
// 00000000 00000000 00000000 00000000
// 这个数据会被分为
// 0 00000000 00000000000000000000000
// S E M
(2) double
double占64个字节,S占1个字节,E占11个字节,M占52个字节。
//double
//例如
// 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
// 这个数据会被分为
// 0 00000000000 0000000000000000000000000000000000000000000000000000
// S E M
3. 储存规定
对于M来说,M的范围为, M >=1 && M < 2,那么因为M的表示方法必然为“1.”……,所以直接默认最高位的1,这样能够提高浮点数M多了一位,精度提高了。
对于E来说,因为需要E有正负,而正负数在不加符号位时不好表示,为了提高效率,编译器会默认对于float型,E的值等于实际的大小+127。对于doublet型,E的值等于实际的大小+1023。也就是说如果E == 0,那么E的部分会分别被置为01111111和01111111111。
实际的理解还需要举例,接下来给出一个特殊的数字,体验一下浮点数的存储规则吧。
float f = 5.5f;
//转换为2进制->101.1
//提取指数部分->1.011*2^2
//转换为标准形式->(-1)^0 * 1.011 * 2^2
//最后还原为存储方式 S = 0, M = 011, E = 2 + 127
//结果为:
// 0 10000001 01100000000000000000000
// S E M
那么现在来解释一下为什么之前打印n的时候出现了一个特别大的数。
float f = 9.0f;
//转换为2进制->1001
//提取指数部分->1.001*2^3
//转换为标准形式->(-1)^0 * 1.001 * 2^3
//最后还原为存储方式 S = 0, M = 001, E = 3 + 127
//结果为:
// 0 10000010 00100000000000000000000
// S E M
实际上,将这个数以%d转换为十进制整数打印就刚好能得到109157616这个数。
作者结语
对于数据结构而言,浮点型整数型的存储只是占了一小部分。实际上还包括结构体。其中线性数据结构有:顺序表,链表,栈,队列。树形数据结构还包括二叉树。等等内容。这些内容将会在下一篇博客之中介绍,这里先预告一下。