数据的存储
目录
数据类型介绍
类型的基本归类
整形在内存中的存储
原码反码补码
机器的大小端
练习
浮点数在内存中的存储
如何存
如何取
数据类型介绍
前面我们已经学习了基本的内置类型,以及他们所占存储空间的大小。
char //字符数据类型
short //短整型
int //整形
long //长整型
long long //更长的整形
float //单精度浮点数
double //双精度浮点数
注意:C语言没有字符串类型!!!
类型的意义:
1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。
2. 如何看待内存空间的视角。
类型的基本归类
整形家族:
char
unsigned char
signed char
short
unsigned short[int]
signed short[int]
int
unsigned int
signed int
long
unsigned long[int]
signed long[int]
浮点数家族:
float
double
构造类型:
数组类型
结构体类型
枚举类型
联合类型
指针类型:
int pi;
char pc;
float pf;
void pv;
空类型:
void表示空类型(无类型),通常应用于函数的返回类型、函数的参数、指针类型
整形在内存中的存储
我们都知道,创建变量是要在内存中开辟空间的,给变量赋值本质是将值存放在了申请的内存空间上,而计算机只认识0/1,那么一个数在内存中是如何存储的呢???
原码反码补码
计算机中的整数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示正,用1表示负,而对于数值位,正数和负数的表示方法不同:
负数
原码:直接将原数按照正负数的形式翻译成二进制就可以
反码:原码的符号位不变,其他位按位取反
补码:反码+1
正数:原码反码补码是一样的
重点:在内存中,存储的是整数的补码形式
原因:在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

我们分别定义了一个正整数和一个负整数,并手动推到了原码反码补码以及十六进制表示,然后调试观察a和b在内存中的存储,发现整体和我们推导的一致,内存中的确存储的是整数的补码形式,但是字节的顺序好像不太一致~
机器的大小端
大小端的概念
大小端,或称为字节序,指的是在计算机系统中,对于一个多字节的数据类型(如int, short, long等),其各个字节在内存中是如何存放的。(因为内存单元划分的最小单元就是字节,所以不会存在小于1个字节的比特位的存放顺序,只会存在大于1个字节的各个字节如何存放的问题)
大端模式:数据的高位字节存放在内存的低地址处,低位字节存放在内存的高地址处。
小端模式:数据的低位字节存放在内存的低地址处,高位字节存放在内存的高地址处
这里的“高位字节”和“低位字节”可以类比我们书写数字:在数字 0x12345678 中,12 是最高位(万位),78 是最低位(个位)。
大小端的适用场景
• 大端模式的优点在于符号位存在于第一个字节,容易快速判断数据的正负和大小。
• 小端模式的优点在于,当进行数据类型转换(例如从 short 转换为 int)时,不需要调整字节内容,因为低位字节就在低地址。
注:大多数的机器都采用的小端模式
判断机器的大小端
#include <stdio.h>
int check_sys()
{int a = 1;//地址: 低 -> 高 //大端: 00 00 00 01//小端: 01 00 00 00return *(char*)&a;
}int main()
{if (check_sys() == 1)printf("小端\n");elseprintf("大端\n");return 0;
}
练习
总结:我们来总结一下常见类型的数值的取值范围:
signed char
//以下展示的都是补码
0000 0000 0
0000 0001 1
0000 0010 2
0000 0011 3
...
0111 1111 127
1000 0000 -128(直接被解析成-128)
1000 0001 -127
...
1111 1110 -2
1111 1111 -1
unsigned char
0000 0000 0
0000 0001 1
0000 0010 2
0000 0011 3
...
0111 1111 127
1000 0000 128
1000 0001 129
...
1111 1110 254
1111 1111 255
同理可得:
signed int: [-2^31,2^31-1]
unsigned int: [0,2^32-1]
其他类型依此类推
eg1:
#include <stdio.h>
int main()
{char a = -1; signed char b = -1;unsigned char c = -1;printf("a=%d,b=%d,c=%d", a, b, c); //-1, -1, 255return 0;
}
a/b:
原码: 10000000 00000000 00000000 00000001
反码: 11111111 11111111 11111111 11111110
补码: 11111111 11111111 11111111 11111111
存放到char类型变量中发生截断:
11111111
打印的时候整形提升, 由于是%d, 所以补符号位
补码: 11111111 11111111 11111111 11111111
反码: 11111111 11111111 11111111 11111110
原码: 10000000 00000000 00000000 00000001
所以打印结果是-1c:
原码: 10000000 00000000 00000000 00000001
反码: 11111111 11111111 11111111 11111110
补码: 11111111 11111111 11111111 11111111
存放到char类型变量中发生截断:
11111111
打印的时候整形提升, 由于是%d, 所以直接补0, 无符号数也不存在原码补, 直接打印即可
00000000 00000000 00000000 11111111
所以打印结果是255
eg2:
#include <stdio.h>
int main()
{char a = -128;printf("%u\n", a);return 0;
}
a:
原码: 10000000 00000000 00000000 10000000
反码: 11111111 11111111 11111111 01111111
补码: 11111111 11111111 11111111 10000000
存放到char中, 发生截断
10000000
又因为格式化是%u, 所以先要进行整形提升, a是char类型, 所以补符号位1
11111111 11111111 11111111 10000000
因为是以%u打印, 所以printf认为是一个无符号数, 那么直接计算即可
所以打印 4294967168
eg3:
#include <stdio.h>
int main()
{char a = 128;printf("%u\n", a);return 0;
}
a 原/反/补: 00000000 00000000 00000000 10000000
存放到char中, 发生截断
10000000
又因为格式化是%u, 所以先要进行整形提升, a是char类型, 所以补符号位1
11111111 11111111 11111111 10000000
因为是以%u打印, 所以printf认为是一个无符号数, 那么直接计算即可
所以打印 4294967168
eg4:
#include <stdio.h>
int main()
{int i = -20;unsigned int j = 10;printf("%d\n", i + j);return 0;
}
i:
原码: 10000000 00000000 00000000 00010100
反码: 11111111 11111111 11111111 11101011
补码: 11111111 11111111 11111111 11101100j 原反补:
00000000 00000000 00000000 0000101011111111 11111111 11111111 11101100 -i补码
00000000 00000000 00000000 00001010 -j补码
11111111 11111111 11111111 11110110 相加-补码
11111111 11111111 11111111 11110101 反码
10000000 00000000 00000000 00001010 原码
-10
eg5:
#include <stdio.h>
int main()
{unsigned int i;for (i = 9; i >= 0; i--){printf("%u\n", i);}return 0;
}
结果是打印死循环,因为unsigned int不可能是负数,范围是[0,2^32-1]
eg6:
#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)); //255return 0;
}
strlen遇到'\0'才结束并且统计字符个数不包含'\0','\0'的字面值是0,因此这道题的本质就是求循环了多少次a[i]是0,根据上面的圆形图,我们知道,从-1到-128是128个数,从127到1,是127个数,加起来是255
eg7:
#include <stdio.h>
unsigned char i = 0;
int main()
{for (i = 0; i <= 255; i++){printf("hello world\n");}return 0;
}
死循环打印 hello wolrd,因为unsigned的最大值就是255,永远不会跳出循环
浮点数在内存中的存储
一个例子:
#include <stdio.h>
int main()
{int n = 9;float* pFloat = (float*)&n;printf("n的值为:%d\n", n); //9printf("*pFloat的值为:%f\n", *pFloat); //0.000000*pFloat = 9.0;printf("num的值为:%d\n", n); //1091567616printf("*pFloat的值为:%f\n", *pFloat); //9.0return 0;
}
我们虽然目前不完全清楚上述代码为啥是这样的打印结果,但我们可以得到初步的结论:整形在内存中存储方式和浮点数在内存中存储方式必然不同!否则按照整数存进去,按照浮点数取出来应该结果相同呀;按照浮点数存进去,按照整数取出来,结果也应该相同!
如何存
根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式

例如十进制的5.5,二进制是 101.1,写成科学计数法就是 -1^0 * 1.011 * 2^2,S是0,M是1.011,E是2
存储时有效数字M的规定:
前面说过, 1≤M<2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分。
IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以32位浮点数为例,留给M只有23位,将第一位的1舍去以后,等于可以保存24位有效数字
存储时数字E的规定:
首先,E为一个无符号整数(unsigned int) 这意味着,如果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。
下面我们举例说明5.5是如何在内存中存储的

如何取
E不全为0或不全为1:
E-127,有效数字M前加 “1.” 即可
E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字
E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)
下面我们来解释一下最开始代码输出的结果
#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("num的值为:%d\n", n); //③printf("*pFloat的值为:%f\n", *pFloat); //④return 0;
}
①: 以整数形式存储, 以整数形式获取, 得到的就是9②:
9在内存中的存储:
00000000 00000000 00000000 000001001
以浮点数方式获取, 会如何看待存储方式
0(S) 00000000(E) 000000000000000000001001(M)
E是全0, 因此是很小的一个数, -1^0 * 0.000000000000000000001001 * 2^(-127)
%f打印, 精度到第六位, 因此打印0.000000③
9.0的科学计数法: (-1)^0 * 1.001 * 2^3
在内存中的存储:
0 10000010 00100000000000000000000
以整数的方式获取, 会当成 01000001000100000000000000000000 求原码, 最高位是0, 原码还是该数
打印出来是 1,091,567,616④: 以浮点数形式存储,以浮点数形式获取, 得到的就是9.0


