亲爱的,你好,看到这篇文章咱们即是有缘🐱🐉O(∩_∩)O,不耽误大家宝贵的时间,咱们直接开始正题。今天我们要学习的是C语言中的数据在内存中是如何存储的。
数据在内存中的存储
本章重点
数据类型详细介绍
整型在内存中的存储:原码、反码、补码
大小端字节序介绍及判断
浮点型在内存中的存储及解析
1.数据类型的介绍
之前我们学过以下数据类型
数据类型 | 释义 | 可向内存申请空间大小(字节) |
---|---|---|
char | 字符类型 | 1 |
short | 短整型 | 2 |
int | 整型 | 4 |
long | 长整型 | 4(win32) / 8(win64) |
long long | 更长的整型 | 8 |
float | 单精度浮点型 | 4 |
double | 双精度浮点型 | 8 |
long double | 多精度浮点类型 | 8/10/12/16(取决于编译器,C99标准之后才有) |
意义:
1.知道使用这个类型开辟内存空间的大小(大小决定使用范围)
2.如何看待内存空间的视角
1.1类型的基本归类
整型家族:
char:为啥char是整型家族呢?它不是字符类型吗?大家可能会有这样的疑惑。其实,char类型在存储字符时,存储的是字符的ASCII值,ASCII值是整数。 另外,char 到底是unsigned char 还是 signed char ?其实这取决于编译器,不过大部分编译器认为char 是signed char。
unsigned char:无符号的字符类型
signed char:有符号的字符类型
short :相当于signed short [int]
unsigned short [int] :无符号的短整型
signed short [int] 有符号的短整型
int :相当于 signed int
unsigned int :无符号的整型
signed int :有符号的整型
long:相当于signed long [int]
unsigned long [int]:无符号的长整型
signed long [int]:有符号的长整型
大家看到这里可能会想为啥会分有符号的和无符号的呢?
生活中我们知道温度 有5°C 也有 -20°C ,这里的5 和 -20 就是有符号的数,它们可以存放到有符号的类型里面,另外,我们知道年龄都是正数,它们就可以存放到无符号的类型里面。
浮点数家族:
float double long double 于前面类似,这里不一一说明了。
构造类型:
数组类型:int arr[10]; 此数组的类型是int [10],数组去掉数组名便是数组的类型。
结构体类型:struct
枚举类型:enum
联合类型:union
指针类型:
int* pi; char* pc; float* pf; void* pv; 指针是用来存放地址的。
空类型:通常应用于函数返回类型,函数形参,指针类型。
2.整型在内存中的存储
2.1原码、反码、补码
整型在内存中的存储有三种表示方法,即原码,反码,补码
三种表示方法均有符号位和数值位,最高位为符号位,“0”表示正,“1”表示负。
正整数三码相同,包括0,而负整数各码不同。
原码:
直接将数据以二进制位表示即可
反码:
原码的符号位不变,其他位按位取反即可
补码:
反码+1即可
int main()
{
int a = 10;
//原码:00000000000000000000000000001010
//反码:00000000000000000000000000001010
//补码:00000000000000000000000000001010
int b = -10;
//原码:10000000000000000000000000001010
//反码:11111111111111111111111111110101
//补码:11111111111111111111111111110110
return 0;
}
对于整型来说:数据存放内存中其实存的是补码
为什么呢?
使用补码,可以将符号位和数值位统一处理。
同时加法和减法也可以统一处理(CPU处理器)此外,补码和原码可以相互转换,不需要额外的硬件电路元件。
对了,这里给大家补充点知识。
2.2练习
int main()
{
unsigned int ch = -10;
printf("%u\n", ch);
return 0;
}
%u 打印无符号的整型,就算内存中存的不是无符号,也按无符号进行打印。
%d 打印有符号的整型,就算内存中存的是无符号的,也按有符号进行打印。
3.大小端字节序存储
看到这标题,大家可能会想,什么是大小端字节序,为什么会有大小端字节序存储?
因为在计算机系统中,是以字节为单位的,每一个地址单元对应这一个字节,一个字节是8个比特位,如char,但我们知道还有2个字节的short,4个字节的int,另外,还有位数大于8的处理器,如16位的,32位,它们的寄存器的宽度大于一个字节,所以必然存在这这些字节在内存中如何存储分布的问题,所以便有了大小端字节序存储。
大端字节序存储:指高位字节处的数据放在低地址,而低位字节处的数据放在高地址。
小端字节序存储:指地位字节处的数据放在低地址,而高位字节处的数据放在高地址。
图解:
3.1练习
题目:编写一个程序,判断当前环境是大端模式还是小端模式。
参考代码1:
int main()
{
int a = 1;
char* pc = (char*)&a;
if (*pc == 1)
{
printf("小端模式\n");
}
else
{
printf("大端模式\n");
}
return 0;
}
参考代码2:
int check_sys()
{
int a = 1;
char* p = (char*)&a;
if (*p == 1)
{
return 1;
}
else
{
return 0;
}
}
int main()
{
int ret = check_sys();
if (ret == 1)
{
printf("小端模式\n");
}
else
{
printf("大端模式\n");
}
return 0;
}
参考代码3:
int check_sys()
{
int a = 1;
char* p = (char*)&a;
return *p;
}
int main()
{
int ret = check_sys();
if (ret == 1)
{
printf("小端模式\n");
}
else
{
printf("大端模式\n");
}
return 0;
}
参考代码4:
int check_sys()
{
int a = 1;
return *(char*)&a;
}
int main()
{
int ret = check_sys();
if (ret == 1)
{
printf("小端模式\n");
}
else
{
printf("大端模式\n");
}
return 0;
}
4.前面知识复习练习题:
注意:有符号的数,整型提升高位补的是(补码)的符号位,无符号的数,整型提升高位补的是0;
下面程序会输出什么?
4.1练习一:
int main()
{
char a = -1;
signed char b = -1;
unsigned char c = -1;
printf(“a = %d,b = %d,c = %d”, a, b, c);
return 0;
}
参考答案:
a = -1 b = -1 c = 255
int main()
{
char a = -1;
//原码:10000001
//反码:11111110
//补码:11111111
//现在以%d的形式打印,会发生整型提升 (有符号的数高位补符号位,无符号的数高位直接补0)
//补码:11111111111111111111111111111111(整型提升)
//反码:11111111111111111111111111111110
//原码:10000000000000000000000000000001 (a = -1)
signed char b = -1;
//原码:10000001
//反码:11111110
//补码:11111111
//现在以%d的形式打印,会发生整型提升 (有符号的数高位补符号位,无符号的数高位直接补0)
//补码:11111111111111111111111111111111(整型提升)
//反码:11111111111111111111111111111110
//原码:10000000000000000000000000000001 (b = -1)
unsigned char c = -1;
//原码:10000001
//反码:11111110
//补码:11111111
//无符号,%d打印,整型提升
//补码:00000000000000000000000011111111 (c = 255)
printf("a = %d,b = %d,c = %d", a, b, c);
return 0;
}
4.2练习二:
int main()
{
char a = -128;
printf("%u\n", a);
return 0;
}
参考答案:a = 4294967168
4.3练习三:
int main()
{
char a = 128;
printf("%u\n", a);
return 0;
}
参考答案: a = 4294967168
4.4练习四:
int main()
{
int i = -20;
unsigned int j = 10;
printf("%d\n", i + j);
return 0;
}
参考答案: -10
4.5练习五:
int main()
{
unsigned int i;
for (i = 9; i >= 0; i–)
{
printf("%u\n", i);
}
return 0;
}
参考答案:死循环
4.6练习六:
int main()
{
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
printf("%d", strlen(a));
return 0;
}
参考答案:255
int main()
{
//-128~127
char a[1000];
int i;
for (i = 0; i < 1000; i++)
{
a[i] = -1 - i;
}
// a[i] 的取值 -1 -2 -3 ... -128 127 126 ... 2 1 0 -1 -2...后面就循环了
//0 前字符长度为 255
printf("%d", strlen(a));
//strlen 求字符串长度 求的是'\0'或0之前的长度
return 0;
}
4.7练习七:
int main()
{
unsigned char i = 0;
for (i = 0; i <= 255; i++)
{
printf(“hello world\n”);
}
return 0;
}
参考答案:死循环
int main()
{
unsigned char i = 0;
//于前面一道题类似,unsigned char 的取值范围是0~255,所以i<=255 恒成立,死循环
for (i = 0; i <= 255; i++)
{
printf("%d,hello world\n",i);
}
return 0;
}
5.浮点型在内存中的存储
常见的浮点数:3.14159 1E10
浮点数类型:float double long double
浮点数表示的范围:float.h 中定义
5.1 一个例子
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;
}
运行结果:
5.2浮点数存储规则
num和*pFloat 明明指的是同一个数,为什么在计算机内部被解析的差别这么大呢?
接下来,我将和大家一起学习一下浮点数在内存中的存储
详细介绍:
跟据国际标准IEEE(电子和电气工程协会)754,任意一个二进制浮点数V可以表示成以下形式:
1.(-1)^S * M * 2^ E
2.(-1)^S表示符号位,当S = 1时,V为负数,当S = 0时,V为正数
3.M 表示有效数字 大于等于1,小于2
4. 2^E表示指数位
例如:
浮点数在内存中存储是如何分布的呢?
IEEE 754 对于有效数字M和指数E还有一些特别的规定
M大于等于1,小于2,M可以写出1.xxxxxxx的形式,IEEE 754 规定,这里的1不存储在M中,M中只存储xxxxxxx部分,即小数部分,当计算机内部读取时。在把这个1加上,这样有利于提高精度,原本M只能存1+22位,现在1不存在这里,所以M可以存23位有效数字。如1.01,浮点数内存中只存01。
至于指数E,情况就比较复杂
首先E是一个无符号整数(unsigned int ),对于8位的,它的取值范围是0~255,对于11位的,它的取值范围是0 ~ 2047,我们知道指数E是可以位负数的,所以IEEE 754 规定,对于E的真实值必须加上一个中间数进行存储,对于8位的,加上127,对于11位的,加上1023,比如对于8位的 2 ^ 10, 10+127 = 137,即1000 1001。
E从内存中取出还可以在分三种情况
E不全为0 或 不全为1
这时将采用下面的规则,即E减去127 或1023 得到其真实值,在加上它前面的有效数字M。比如 (32位)0.5 的二进制表示为 0.1 ,要写成M的形式,小数点需要往后移1位,即 1.0 * 2 ^ -1, -1+127 = 126,即0111 1110,所以它在内存中的存储是 0 01111110 00000000000000000000000。现在将它从内存中取出,0111 1110即126 ,126-127 = -1,再加上有效数字M,得(-1)^ 0 *1.0 * 2 ^ -1。
再举一个例子:
E全为0
此时,浮点数的指数E直接为 1-127 或 1-1023 ,有效数字M不在加上前面的1,而是表示成0.xxxxxxx的形式,用来表示±0,以及无限接近0的数字。
E全为1
此时,有效数字M全为0,表示±无穷大(± 表示符号位)
5.3解释前面的一个例子
int main()
{
int n = 9;
//00000000000000000000000000001001
float* pFloat = (float*)&n;
//0 00000000 00000000000000000001001
//S = 0
//E = -126
//M = 00000000000000000001001
//(-1)^0 * 0.00000000000000000001001 * 2^-126 ≈ 0
printf("n的值为:%d\n", n); //9
printf("*pFloat的值为:%f\n", *pFloat); //0.000000
*pFloat = 9.0;
//二进制:1001.0 1.001*2^3
//S = 0
//E = 3 3+127 = 130
//M = 1.001
//0 10000010 00100000000000000000000
printf("num的值:%d\n", n); //1091567616
printf("*pFloat的值为:%f\n", *pFloat);//9.000000
return 0;
//总结:对于同一个码:整型的解析方式与浮点数的解析方式存在很大的区别。
}
5.4 浮点数的相等判断
5.4.1 精度
浮点数的存储是有精度损失的 (实验验证了)
5.4.2 浮点数不能用==判断相等
5.4.3 判断浮点数是否相等的正确方式
下面两种方式均可
#define EPSION 0.00000001 //精度,通常宏定义
int main()
{
double x = 1.0;
double y = 0.1;
if (((x - 0.9) - 0.1) > -EPSION && ((x - 0.9) - 0.1) < EPSION)
{
printf("you can see me!\n"); // 表示(x-0.9)与 0.1 相等
}
else
{
printf("oops!\n");// 表示(x-0.9)与 0.1不相等
}
return 0;
}
#include <math.h>
#include <float.h> // 使用系统精度,如DBL_EPSILON , FLT_EPSILON 时,需引用头文件
int main()
{
double x = 1.0;
double y = 0.1;
if (fabs((x - 0.9) - y) < DBL_EPSILON)
{
printf("you can see me!\n");// 表示(x-0.9)与 0.1 相等
}
else
{
printf("oops!\n");// 表示(x-0.9)与 0.1不相等
}
return 0;
}
5.4.4 一个浮点数与 0 判断是否相等的方法
#include <math.h>
#include <float.h>
int main()
{
double a = 0.0000000000000000000000001;
//fabs(a - 0.0)
//fabs(a)
if (fabs(a) < DBL_EPSILON)
{
printf("a 与 0.0 相等\n");
}
else
{
printf("a 与 0.0 不相等\n");
}
return 0;
}
5.4.5 = 要不要留?
不知道大家有没有注意到一个细节,上面的(fabs(a) < DBL_EPSILON),
(a > -DBL_EPSILON && a < DBL_EPSILON),为什么我都没有写等于?
xxx_EPSILON 表示最小误差,n+xxx_EPSILON != n 表示 xxx_EPSILON 是使 n 变化的最小正数。很多数加上n 都会使n 发生变化,xxx_EPSILON是它们中最小的那个,但它也会使n 发生变化。
由上我们知道 fabs(x)<=xxx_EPSILON 成立的话,当 = 成立时,x与 xxx_EPSILON 一样,一个数加上它会发生改变,但fabs(x)<=xxx_EPSILON 成立代表x == 0.0, 这与0.0的定义相驳,所以 = 号 不能要!!!。
码字不易,希望能帮助到大家,可以点一个赞吗?哈哈,评论和收藏支持一下哦,感谢大家!期待再次相见!💖💖💖