【C语言】数据的存储


1.前言

从今天开始,我们正式进行C语言的进阶学习。今天要分享的内容的是数据的存储。


2.数据类型介绍

2.1疑问和意义

在C语言中,我们已经了解其基本内置类型,包括char、short、int、float、double、long、long long,但是你知道为什么会有这些类型吗?为什么不设置一种统一的类型?
也就是说这些数据类型有什么意义?1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。这样就可以做到不浪费内存空间。2. 如何看待内存空间的视角。用int向内存申请4个字节的空间,我们认为其存放的是整形,用float向内存申请4个字节的空间,我们认为其存放的是单精度浮点型。


2.2基本归类

  1. 整形家族
char
	unsigned char
	signed char
short
	unsigned short [int]
	signed short [int]
int
	unsigned int
	signed int
long
	unsigned long [int]
	signed long [int]

(1)每种类型都包括signed和unsigned。其中short、int和long都是有符号的,而char是signed还是unsigned取决于编译器,我们常用的编译器都是char = signed char。
(2)char不是存放字符吗?为什么属于整形家族?
char在内存中存储的是ASCII码值,ASCII是整形,所以字符属于整形家族。

  1. 浮点型家族
float
double

浮点型家族都是signed类型的。

  1. 构造类型
数组类型
结构体类型struct
枚举类型enum
联合类型union

构造类型是自定义类型,如数组类型。int arr[10]的类型是int [10],其类型随着元素个数和类型发生变化,所以是自定义类型。其他三种构造类型在后面会讲到,现在暂时不讲。

  1. 指针类型
int*pi
char*pc
float*pf
  1. 空类型
void

void表示空类型(无类型),通常用于函数的返回类型、函数参数、指针类型。


3.整形在内存中的存储

我们都知道整形在内存中存储的是其二进制的补码,但具体是如何存储的我们还不清楚。在前面的学习中,我们已经了解了整形的原码、反码和补码,今天我们先温习下。

3.1原码、反码、补码

  1. 这是整形的3种表现形式。这3种表现形式都有符号位和数值位。符号位决定其正负,0表示正数,1表示负数;数组位则决定数值的大小。
    正数的原码、反码和补码相同。而负数的原、反、补之间需要相互转化。
  • 原码
    直接将数值按照正负数的形式翻译成二进制就可以得到原码。
  • 反码
    将原码的符号位不变,其他位依次按位取反就可以得到反码。
  • 补码
    反码+1就得到补码。
  1. 例子
int a = 20;//原码00000000000000000000000000010100
           //反码00000000000000000000000000010100
           //补码00000000000000000000000000010100
           //整数的原、反、补相同
int b = -10;//原码10000000000000000000000000001010
            //反码11111111111111111111111111110101
            //补码11111111111111111111111111110110

在编译器中,我们可以查看变量的内存,从而了解其存储的方式。在这里插入图片描述
在这里插入图片描述

变量a的补码的十六进制正好是00000014,变量b的补码的十六进制正好是FFFFFFF6。但是在内存中存放的顺序好像是倒着存放的,但好像也不完全倒着存放,这在后面会讲到。我们只需知道整形在内存中以二进制补码的形式存储。

3.2为什么存放的是补码?

  1. 原因:在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。
  2. 例子
int a = 1;
int b = -1;
int c = a+b;
printf("%d",c);

CPU只有加法器,所以1-1会被转换成1+(-1),假设使用原码计算

1的原码:00000000000000000000000000000001
-1的原码:10000000000000000000000000000001
相加的结果:10000000000000000000000000000010

结果为-2,所以用原码计算存在问题,我们尝试用补码计算

1的补码:00000000000000000000000000000001
-1的补码:11111111111111111111111111111111
相加的结果00000000000000000000000000000000

结果为0,使用补码计算得到的还是补码,如果要得到这个数,我们就需要进行原、反、补的转换。但在本例中,最高位的1被截断,变为0,0表示正数,正数的原、反、补相同,所以无需进行转换。

3.3大小端介绍

前面我们提到数据在内存中好像是倒着存储的?是不是只有这种存储方式?现在就来解决这些问题。

  1. 大小端是大端字节序存储和小端字节序存储的简称,是以字节为单位讨论数据内存的存储顺序。
  2. 大小端字节序存储
int a = 0x11223344;//利用十六进制方便我们查看数据的内存
                   //一个字节等于两个十六进制位
                   //且查看内存时以十六进制的形式显示
                   //将数据转换成十六进制时左边的字节称为高位字节序,
                   //右边的字节称为低位字节序,例如44是地位字节序,11是高位字节序

以下是数据的三种存储方式
在这里插入图片描述
例如我使用的编译器数据的存储是按照小端字节序存储
在这里插入图片描述

  1. 练习
    设计一个小程序来判断当前机器是大端还是小端
int main()
{
	int a = 1;
	char*pc = (char*)&a;
	if(*pc==1)
	{
		printf("小端");
	}
	else
	{
		printf("大端");
	}
	return 0 ;
}

关键
只访问第一个字节,如果是1就是小端,如果是0就是大端。
在这里插入图片描述

3.4练习

int main()
{
	char a = -1;
	signed char b = -1;
	unsigned char c = -1;
	printf("%d %d %d",a,b,c);
	return 0 ;
}

答案是-1 -1 255

思路
将整形的-1存放到char类型中需要进行截断,用%d打印有符号的整形,需要先进行整形提升,再转换成原码进行打印。在这里插入图片描述

a和b的原理类似,但c不同,不同在整形提升这个环节,c是unsigned char类型,高位不是符号位,高位补0,00000000000000000000000011111111,正数的原、反、补相同,所以直接进行打印,无需转换成原码。

int main()
{
	char a = -128;
	printf("%u",a);//%u表示打印无符号的整形
	return 0;
}

答案4294967168

思路
在这里插入图片描述

int main()
{
	char a = 128;
	printf("%u",a);
	return 0 ;
}

答案4294967168

思路
在这里插入图片描述

int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%d",i+j);
	return 0 ;
}

答案-10

关键
%d打印有符号的int,i的补码加上j的补码后,需要转换成原码,但如果用%u打印无符号的int,无需进行转换,直接打印。

int main()
{
	unsigned in i ;
	for(i = 9;i>=0;i--)
	{
		printf("%u",i);
	}
	return 0;
}

答案是死循环

关键
i的类型是unsigned int,不可能<0,当i=0,i–不可能变成-1,而是变成一个非常大的数字。

int main()
{
	char a[1000];
	int i;
	for(i = 0;i<1000;i++)
	{
		a[i] = -1-i;
	}
	printf("%d",strlen(a));
	return 0 ;
}

答案255

关键
数组a中内容有-1,-2,-3,……,-127,-128,127,126,……2,1,0…………等等。char是signed char,其取值范围为-128~127,所以最小值是-128,最大值是127。当遇到0时,就可以算出数组长度,因为strlen求出0之前的数组长度。在这里插入图片描述

int main()
{
	unsigned char i = 0;
	for(i = 0;i<=255;i++)
	{
		printf("%d",i);
	}
	return 0 ;
}

答案是死循环

关键
i<=255恒成立,当i=255时,i++后i变成0,重新循环。


4.浮点型在内存中的存储

根据国际标准IEEE(电气和电子工程协会) 754,

  1. 任意一个二进制的浮点数V在内存中可以表示为

(-1) ^S * M * 2 ^E
(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。

例如V=5.5,转换成二进制101.1,转换成科学记数法1.011 *2 ^2,再次转换成 (-1) ^ 0 * 1.011 * 2 ^2。S = 0,M = 1.011,E = 2。

  1. 任意浮点数只有S,M,E不同,所以只需存储S,M,E即可。

  2. 对于32位的浮点数,最高的一位为符号S,接着8位是指数E,剩下的23位是有效数字M
    ;对于64位的浮点数,最高的一位为符号S,接着11位是指数E,剩下的52位是有效数字M。在这里插入图片描述
    很多浮点数无法精确保存,float后只能存放23位小数,double只能存放52位小数。

  3. (1).S直接放进去第一位。
    (2).M总是>=1和<2,所以1可以直接省略,将来拿出来使用时再加上1,若将1也存放进去,就少了一位的精度,比如M = 1.01,只存放01,拿出来使用时再加上1。
    (3).E的情况有些复杂,E是一个无符号的整数,但是通过科学计数法E可以是负数,所以在存放进去时要加入一个中间值,8位的要加上127,11位的要加上1023。
    当E不全为0或不全为1时,E的真实值就是8位或者11位上的二进制转换的整数减去127或者1023,M的真实值就加上1;当E全为0,浮点数的指数E等于1-127(或者1-1023)即为真实值,M的真实值不再加上1,直接表示为0.xxxx。这样做是为了表示±0,以及接近于
    0的很小的数字;当E全为1,有效数字M全为0 ,表示±无穷大(正负取决于符号位s)。

  4. 例子

float f = 5.5f;

用科学计数法表示(-1) ^ 0 * 1.011 * 2 ^ 2。其中S = 0,M = 1.011,E = 2。用二进制表示为0 10000001 01100000000000000000000。注意M要减去1,E要加上127,M后面不够的补0。

float f = 9.0f;

用科学计数法表示为(-1) ^ 0 * 1.001 * 2 ^ 3。其中S = 0,M = 1.001,E = 3.用二进制表示为 0 10000010 00100000000000000000000。

  1. 注意
int main()
{
	int n = 9;
	float * pf = (float *)&n;//以整形视角将数据存放在内存中
	//0 00000000 00000000000000000001001
	printf("%d",n);
	printf("%f",*pf);//V = (-1)^0*0.……1001*2^(-126)
	*pf = 9.0;//以浮点数的视角将数据存放到内存中
	//0 10000010 00100000000000000000000
	printf("%d",*pf);
	printf("%f",*pf);
	return 0 ;
}

答案9 0.000000 1091567616 9.000000

以整数形式放进去,只能以整数形式拿出来,以浮点数形式放进去,只能以浮点数形式拿出来。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值