C语言数据的存储(附练习巩固)

前言:这篇文章我们重点讲解整形在内存中存储以及浮点型在存中的存储在此之前,我们先简单回顾下数据基本类型有哪些。

目录

一、数据类型的分类

1.1 整形类型

1.2 浮点类型

1.3 构造类型

1.4 指针类型

1.5 空类型

 二、整形在内存中的存储

2.1、原码

2.2、反码

2.3、补码

示例:

2.4 大小端字节序存储

2.5 练习:

三、浮点型在内存中的存储

3.1浮点数的存储规则:


一、数据类型的分类

1.1 整形类型

char //是unsigne 还是 signed 取决于编译器
unsigned char
signed char

//short int long 默认为 signed 型

short  
unsigned short [int]
signed short [int]

int
unsigned int
signed int

long
unsigned long [int]
signed long [int]

1.2 浮点类型

float
double

1.3 构造类型

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

1.4 指针类型

int * p1;
char * p2;
float * p3;
void * p4;

1.5 空类型

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

 二、整形在内存中的存储

整数在计算机中的存储形式分为三种,即原码,反码,补码.

三种存储方式分为符号位和数值位两部分,符号位用0表示正,1表示负,数值位均为二进制数。(整数的原码反码补码相同

2.1、原码

直接将数值按照二进制的形式进行翻译即可。

2.2、反码

对原码的符号位不进行变化,其他位按位取反即可。

2.3、补码

将反码+1即可得到补码。

示例:

C语言数据的存储(附练习巩固)_第1张图片

 那么对于整形来说,存放在内存中的形式是补码(原因如下)

(通过内存窗口进一步确认以上观点):

C语言数据的存储(附练习巩固)_第2张图片

因为采用补码的形式, 符号位和数值位可以进行统一的处理。(cpu只支持加法运算)

C语言数据的存储(附练习巩固)_第3张图片

2.4 大小端字节序存储

概念:

大端(存储)模式,是指数据的低位字节保存在内存的高地址中,而数据的高位字节,保存在内存的低地址中;
小端(存储)模式,是指数据的低位字节保存在内存的低地址中,而数据的高位字节,   保存在内存的高地址中。

为什么会有大小端呢?

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8bit。但是在C语言中除了8 bitchar之外,还有16 bitshort型,32 bitlong型(要看具体的编
译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
例如:一个 16bit short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11
高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高地址中,即 0x0011 中(两个16进制数是一个字节)。对于小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
示例:

在vs x86环境下:

C语言数据的存储(附练习巩固)_第4张图片

 C语言数据的存储(附练习巩固)_第5张图片

2.5 练习:

2.5.1.

设计一个小程序来判断当前机器的字节序。

  #include
int main()
{
	
	int a = 1;
	char * p = (char*)&a;//取出地址的前两个16进制数(一个字节)
	 
	if (*p == 1) {
		printf("小端\n");
	}
	else {
		printf("大端\n");
	}
    return 0;
};

2.5.2:

//输出什么?
#include 
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;
}

答案是: -1 -1 255

下面我们来详细解析这道题:

我们上面说到 整形 在内存 中是按照补码来进行存储的,-1 的补码是11111111111111111111111111111111,因为要存入char类型(8个bit位),所以发生了 截断高位截断,低位保留)。变成了 11111111(还是补码形式),根据需求,是按照 %d 形式进行打印,这时针对 (char 类型的 11111111)进行了  整形提升  (根据数据的类型进行提升:如果是有符号就是按照高位补符号位的原则,如果是无符号位就按照高位补0的规则),提升为:11111111111111111111111111111111,之后 再将其转化为 原码(因为是%d打印,所以将其视为有符号数) 10000000000000000000000000000001 ,打印结果为 -1 .(这里 signed char 等同与char,C语言无规定,视编译器而定)

255 是如何打印出来的呢?

-1 的 补码 是 11111111111111111111111111111111,与上面相同  int  类型的数据存入 char 类型时 发生截断,变为 11111111,因为以 %d 形式进行打印 发生整形提升,此时因为是 无符号类型 所以高位是补0,变为 00000000000000000000000011111111 ,将其转化为 原码(%d 的效果就是把它视为 正数,又因为正数“原”“反”“补”相同,所以上述其实就是答案的二进制形式) 进行打印 ,结果为 255.

 2.5.3:

#include 
int main()
{
	
	char a = -128;
	
	printf("%u", a);

	return 0;

}

:打印结果为 4294967168 ,-128 补码是 11111111111111111111111110000000 ,存入char类型的时候发生了截断,变为 10000000 .然后因为以 %u 形式进行打印,整形提升为了:11111111111111111111111110000000,后因为是无符号打印(把它视为了正数),正数原反补都相同,所以结果为:4294967168

2.5.4

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

}

:答案:-10,这里不要简单的以为是 -20 + 10 为 -10. 把-20 转换为补码:11111111111111111111111111101100.  10 转换为补码为:000000000000000000000000 00001010.将其相加 :11111111111111111111111111110110,以为以%d进行打印(将其视为正数),所以转化为原码:10000000000000000000000000001010 ,十进制为:-10

2.5.5

#include 
int main()
{
	unsigned int i;
	 
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}

}

 :结果 死循环,依次打印9~0后,打印4294967295,依次减小,后反复进行以上操作。

当减小到-1后 本应该停止,但是因为i的类型为无符号整形,所以在其看来 -1 并非是个负数,-1的补码为 11111111111111111111111111111111,将其视为正数,所以为 4294967295

2.5.6

#include 
int main()
{
	int main()
	{
		char a[1000];

		int i;

		for (i = 0; i < 1000; i++)
		{
			a[i] = -1 - i;
		}

		printf("%d", strlen(a));

		return 0;
	}
}

: 数组下标 i 是从 0~999 依次增大 要存入char[1000]  类型的数组的值是 -1 ~  -1000。但是整形的数据存入char[1000] 类型的数组会发生变化:

C语言数据的存储(附练习巩固)_第6张图片

 strlen函数是求字符串长度,遇到‘\0’就停止,‘\0’的ASCII码值是0,如上图所示,题意就是把数字按逆时针存入数组中,当下标到129的时候,其实存入数组中的数据变成了127,然后依次减小,到0位置,所以答案为: 128+127 = 255.

2.5.7

#include 

unsigned char i = 0;

int main()
{
	for (i = 0; i <= 255; i++)
	{
		printf("hello world\n");
	}
	return 0;
}

:结果 死循环 因为 unsign char类型 的范围是 0~255 ,无论如何增加,始终逃不出这个范围。

三、浮点型在内存中的存储

常见的浮点数: 3.141592,2E10(2.0*10的十次方,10是指数),浮点型家族包括: float,double,long double类型,float.h中定义了浮点数的表示范围。

我们先看一个案例:来佐证浮点型数据存储方式是与整形数据存储不一样的。

#include 

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;
}

运行的结果是:

n的值为:9
*pFloat的值为:0.000000
num的值为:1091567616
*pFloat的值为:9.000000

我们发现 *pFloatnum 的值 超出了我们平常的认知(2,3两行),是因为:我们使用一个float*的指针对一个整形的数据进行解引用(它会认为里面放的是浮点型数据,虽然实际是整形)。

num的值也是类似,以%d形式进行打印(会把浮点型数据,视为整形)一个浮点型数据。;

总结一个结论整数浮点数在内存中存储方式是有所差异

3.1浮点数的存储规则:

根据国际标准IEEE(电气和电子工程协会)754,任意一个二进制浮点数V可以表示成下面的形式:

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

这里我们举个例子来说明:比如5.5用IEEE标准:(-1)^0 *1.011*2^2, 0就是上述的sE就是2,M就是1.011.

我们发现:上述只要将 s,M,E 三个数存储起来(其他都是固定数值),到时候就可以还原出这个浮点型数据。

C语言数据的存储(附练习巩固)_第7张图片IEEE 754对有效数字M和指数E,还有一些特别规定


前面说过, 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是可以出
负数的(比如:对于0.5来说,它用IEEE标准:(-1)^0 * 1.0 * 2^(-1) ,这里E为 -1 ),所以IEEE 754规定,存入内存时 E 的真实值必须再加上一个中间数,对于8位(32bit)的E,这个中间数
127;对于11位(64bit)的 E,这个中间
数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即
10001001。

下面我们来用示例验证一下:

 C语言数据的存储(附练习巩固)_第8张图片

然后,指数E从内存中取出  还可以再分成三种情况:


E不全为0或不全为1
这时,浮点数就采用下面的规则表示,即指数E的计算值减去127(或1023),得到真实值,再将
有效数字M前加上第一位的1。


E全为0
这时,浮点数的指数E等于1-127(或者1-1023)即为真实值,
有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于
0的很小的数字。

E全为1
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s)。

介绍完了浮点型的存储方式,我们再回来看刚刚那道题;

C语言数据的存储(附练习巩固)_第9张图片


 

你可能感兴趣的:(c语言)