文章目录
- Ⅰ 数据类型介绍
- ⒈类型的基本分类
- Ⅱ 整形在内存中的存储
- ⒈原码、反码、补码
- ⒉大小端介绍及判断大小端
- Ⅲ 浮点型在内存中的存储
- ⒈浮点数在内存中的存储规则
- ⒉IEEE 754 对 M 和 E 的存取规定
- ⒊解释前面的题目
Ⅰ 数据类型介绍
基本内置类型
类型 | 类型名称 |
---|---|
char | 字符数据类型 |
short | 短整型 |
int | 整形 |
long | 长整形 |
long long | 更长的整形 |
float | 单精度浮点型 |
double | 双精度浮点型 |
类型的意义
- 使用这个类型开辟内存空间的大小(大小决定了使用范围)。
- 如何看待内存空间的视角。
⒈类型的基本分类
1. 整形类型
- char
- [X] unsigned char
- [X] signed char
- short
- [X] unsigned short (int)
- [X] signed short (int)
- int
- [X] unsigned int
- [X] signed int
- long
- [X] unsigned long (int)
- [X] signed long (int)
- 注:字符在内存中存储的是字符的 ASCII 码值,ASCII 码值是整形,所以字符类型归类到整形类型。
2. 浮点数类型
- float
- double
3. 构造类型(自定义类型)
- 数组类型
- 结构体类型 struct
- 枚举类型 enum
- 联合类型 union
5. 指针类型
- int* pi
- char* pc
- float* pf
- void* pv
6. 空类型
- void 表示空类型(无类型)
- 通常应用于函数的返回类型、函数的参数、指针类型。
int test1(void); //test1 函数没有参数
void test2(int a); //test2 函数没有返回值
Ⅱ 整形在内存中的存储
⒈原码、反码、补码
1. 数据在内存中存储的都是二进制
- 计算机能够处理的都是二进制的数据。
- 整形和浮点型数据在内存中都是以二进制的形式进行存储的。
- 整数在内存中存的都是二进制补码。
2. 整形的二进制表示形式
- 整形的二进制表示形式有 3 中:原码、反码、补码。、
- 正整数:原码、反码、补码相同。
- 负整数:原码、反码、补码需要进行计算。
3. 负整数原反补的计算方法
- 对于负整数来说,(原码 → 补码)或者(补码 → 原码)的方法都是一样的。
- 统一进行取反加1的操作即可。
- 例:算出 -10 的反码,再将 -10 的反码转回原码。
//原码 → 补码,可以采用取反加 1 的操作
10000000 00000000 00000000 00001010 //-10 的原码
11111111 11111111 11111111 11110101 //-10 的反码
11111111 11111111 11111111 11110110 //-10 的补码
//补码 → 原码,也可以采用取反加 1 的操作
11111111 11111111 11111111 11110110 //-10 的补码
10000000 00000000 00000000 00001001 //先取反
10000000 00000000 00000000 00001010 //再加 1。-10 的原码就还原出来了
5. 补码存放整数的特征
- 当符号为为 0 的时候,后边的 1 越多,整数的值就越大;
- 当符号位为 1 的时候,后边的 0 越多,整数的值就越小。
6. 整数在内存中存补码的原因
- 使用补码,可以将符号为数值位统一进行处理。
- 同时,加法和减法也可以统一处理(CPU 只有加法器),又因为补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
- 例:分析 CPU 对 1-1 这个操作是如何计算的
- 因为 CPU 只有加法器,所以 1-1 会被转换成 1 + (-1) 来进行计算。
- 如果采用原码的方式进行计算的话,结果就会出大问题了。
00000000 00000000 00000000 00000001 // 1 的原码
10000000 00000000 00000000 00000001 //-1 的原码
10000000 00000000 00000000 00000010 //1+(-1),结果居然变成了 -2,显然很有问题
- 所以补码这种东西就这么应运而生了。
00000000 00000000 00000000 00000001 // 1 的补码
11111111 11111111 11111111 11111111 //-1 的补码
10000000 00000000 00000000 000000000 //1+(-1),进位产生第 33 个比特位,最高位的 1 就没用了
- 将最高位的 1 丢掉之后,补码相加的结果就是 0 了。
00000000 00000000 00000000 00000000
⒉大小端介绍及判断大小端
- 平时有观察过整型变量在内存中的存储的话就应该发现,整型的值在内存中是倒着存放的。
1. 大小端的定义
- 大端字节序存储:把一个数据的低位字节处的数据存放在内存的高地址处,高位字节的内容放在内存的低地址处。
- 小端字节序存储:把一个数据的高位字节处的数据存放在内存的高地址处,低位字节的内容放在内存的低地址处。
2. 数据的低位和高位
- 如 123 有个十百位,从右往左 3 为低位,1为高位。
- 一个数据也有低位与高位,从右往左分别为低位 → 高位。
- 如:0x11223344,这样一个 16 进制 4 个字节的数据从右往左按照字节划分高低位。
3. 大小端存在的原因
- 在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,。但是在C语言中除了1 个字节的 char 之外,还有 2 个字节的 short型,4 个字节的 int 型等等。
- 由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
int a = 0x11223344;
//a 所占的 4 个字节每个字节分别存着 11 22 33 44
//这 4 个字节的内容该以什么样的顺序往内存里存,就看当前机器的大小端字节序了。
4. 判断当前机器的大小端字节序
- 拿出整型数据 1 存在内存中的第一个字节的内容进行判断,如果是 1 则为小端,为 0 则为大端。
#include <stdio.h>
int check_sys()
{
//对 pa 解引用访问 a 的第一个字节的内容,
//返回的结果是 1 则为小端,0 则为大端
int a = 1;
char* pa = (char*)&a;
return *pa;
}
int main()
{
if (check_sys)
{
printf("当前机器为小端字节序\n");
}
else
{
printf("当前机器为大端字节序\n");
}
return 0;
}
Ⅲ 浮点型在内存中的存储
- 浮点数在内存中也是以二进制的形式存储的,但存储的方式并不是原码、反码、补码这样。
常见的浮点数
- 3.1415926
- 1E10:科学计数法的表示形式,1E10 表示为 1.0 * 1010。
- 浮点数包括:float、double、long double 类型。
- 浮点数表示的范围:float.h 中定义。
用一个例子引出浮点数的存储规则
- n 是以整型的形式存进去的,① 也是用整型的形式打印的,所以这个 9 倒没什么问题。
- 将 n 强转后赋给 pFloat,但是 n 在内存中还是以整数的形式存储的,② 以浮点型的方式取出却是这么个结果,说明整数和浮点数的存储形式是不一样的。
- 通过浮点数的视角,往 n 里面放个 9.0 进去,③ 将 n 里存的 9.0 以整型的形式打印,又一次说明整数和浮点数的存储规则不一致。
- 以浮点数的形式放 9.0 进去,又以浮点数的形式往外拿,④ 的打印结果是 9.0 也没什么问题。
- 打印的数据和打印的格式的类型一定要匹配。
⒈浮点数在内存中的存储规则
- 想要明白 num 和 *pFloat 在内存中明明是同一个数,以浮点数和正数的打印结果却相差这么大,就一定要搞懂浮点数在计算机内部的表示方法。
浮点数的表示形式
根据国际标准 IEEE 754,任意一个二进制浮点数 V 可以表示成 (-1)S * M * 2E 的形式。
- (-1)S:表示符号位,当 S = 0,V 为正数;当 S = 1,V 为负数。
- M:表示有效数字,大于等于 1,小于2。
- 2E:表示指数位。
举个栗子
- 10 进制的 5.5 写成二进制是 101.1,相当于 1.011 * 2 2,然后这个数又是个正数,就可以加上 (-1)0 变成 (-1)0 * 1.011 * 22。
- 那么按照上面浮点数的表示形式可以得出,S = 0,M = 1.011,E = 2。
IEEE 754 规定
- 对于 32 位的浮点数(float),最高的 1 位存储符号位 S,接着的 8 位存指数 E,剩下的 23 位存储有效数字 M。
- 对于64位的浮点数(double),最高的 1 位存着符号位 S,接着的 11 位存着指数 E,剩下的 52 位存着有效数字 M。
⒉IEEE 754 对 M 和 E 的存取规定
1. 有效数字 M 的存储
- 之前说过, M 的范围为 [1,2),也就是说过,M 可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。
- IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的 xxxxxx 部分。
- 比如保存 1.01 时,只保存 01,等到要读取的时候再把第一位的 1 加上去。
- 这样做的目的是节省 1 位有效数字。以 32 位浮点数为例,留给 M 只有 23 位,将第一位的 1 舍去以后,等于可以保存 24 位有效数字。
2. 指数 E 的存储
- 指数 E 位一个无符号整数(unsigned int),这意味着,如果 E 位 8 位,取值范围就是 0 ~ 255。如果 E 为 11 位,取值范围就是 0 ~ 2047。
- 但是,科学计数法中的 E 是可以出现负数的(如:2-1),所以 IEEE 754 规定,存入内存时 E 的真实值必须再加上一个中间数,然后转成二进制数存进内存中。
- 对于 8 位的 E,这个中间数是 127,对于 11 位的 E ,这个中间数是 1023。
- 例如:2-1 的 E 是 -1,在存成 32 位浮点数时,保存成 -1 + 127 = 126,即 0111 1110。在存成 64 位浮点数时,保存成 -1 + 1023 = 1022,即 011 1111 1110。
小试牛刀
- 分析 float f = 5.5 在内存中的存储
- 十进制:5.5 → 二进制 101.1
- 101.1 写成科学计数法的形式为:(-1)^0 * 1.011 * 2^2
- 其中:s = 0,,E = 2(记得加中间值),M = 1.011(只存小数点后的数)。
- 开始存储:0 10000001 01100000000000000000000 // S 存 0,E 存 2+127 = 129 的二进制,M 存 011 后补 0
- 验证:4 位转 1 位的十六进制结果应该是 40 b0 00 00。
- f 也是以小端的形式存储的。
3. 指数 E 从内存中取出
将指数 E 从内存中取来分为三种情况:
- E 不全为 0 或不全为 1:用存进内存中的 E 的值减去中间数(127 或 1023),得到真实值,再将有效数字 M 前加上第一位的 1。
- 例:0.5 的二进制形式为 0.1,由于规定正数部分必须为 1,所以将小数点右移 1 位,则为 1.0 * 2-1。
- 其阶码为 -1 + 127 = 126,表示为 01111110,而尾数 1.0 去掉整数部分为 0,补齐 0到 23 位 00000000000000000000000,则其二进制表示形式为:
0 01111110 00000000000000000000000
- E 全为 0:
- 加了中间数 127 之后往里存结果还是 0,这时浮点数的指数 E 等于 1-127(或者1-1023)即为真实值。
- 此时有效数字 M 不再加上第一位的 1,而是直接还原成 0.xxxxxx。这样做是为了表示 ± 0,以及接近于 0 的很小的数字。
- E 全为 1:
- 8 个位上全是 1 的话,内存中存的就是 255,真实的 E 就是 255 - 127 = 128 了,指数为 128 的话这次直接就无穷大了。
- 此时,如果有效数字 M 全为 0,表示 ± ∞(具体的正负取决于符号位 S)。
⒊解释前面的题目
- 分析 ②:
00000000 00000000 00000000 00001001 //9 的补码
- 站在 pFloat 的角度看 9 的补码,它认为这是个浮点数,那么 pFloat 对 9 的理解就是:
0 00000000 00000000000000000001001 //S、E、M
- 内存中存的 E 为全 0,的 E 为全 0 特殊情况就出现了。此时:
E 还原成 -126
M 还原成 0. 00000000000000000001001
S 还原成 0
- 真实的数字应该是:(-1)^0 * 0.00000000000000000001001 * 2 ^-126,直接就无穷小了。
- 所以打印结果才会是 0.000000
- 分析 ③:
- 十进制 9. → 1001.0 二进制,写成科学计数法:(-1)^0 * 1.001 * 2^3
- S = 0,E = 3,M = 001,开始往里存(E 别忘了加中间数)。
0 10000010 00100000000000000000000 //9.0 的二进制序列
- 以 n 的视角看这段二进制序列 n 任务这是个整数的补码
01000001 00010000 00000000 00000000
- 最后再以 %d 的形式打印,%d 认为 这是个超大的正整数
- 所以打印结果就是 1,091,567,616