你要悄悄努力,然后成为C语言高手--C语言中数据的存储

亲爱的,你好,看到这篇文章咱们即是有缘🐱‍🐉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的定义相驳,所以 = 号 不能要!!!。
请添加图片描述
码字不易,希望能帮助到大家,可以点一个赞吗?哈哈,评论和收藏支持一下哦,感谢大家!期待再次相见!💖💖💖

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值