C语言数据存储深度剖析(保姆级别教学,超级详细,干货满满)

本文详细剖析了C语言中数据存储的原理,包括数据类型介绍、整型与浮点数在内存中的存储方式,以及大小端字节序的判断。通过例题和变式题,深入探讨了整型提升、浮点数的IEEE754标准,并解释了为何通过浮点数视角查看整型数据时会出现意想不到的结果。
摘要由CSDN通过智能技术生成

0.前言:

大家好,今天带给大家的是C语言数据存储深度剖析和一些练习题的讲解,希望能对大家有所帮助
里面穿插了一些零碎但又很重要的知识点,大家会在接下来的内容当中感受到.
大家可以先做一下这几道题,如果有哪一道做错了的话,那么我认为您有必要看一下我的这篇博客,我相信会对你有所帮助

1.例题

T1

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

T2

#include <stdio.h>
int main()
{
    char a = -128;
    printf("%u\n",a);
    return 0;
}

T3

#include <stdio.h>
int main()
{
    char a = 128;
    printf("%u\n",a);
    return 0;
}

T4

int main()
{
	int i= -20;
	unsigned  int  j = 10;
	printf("%d\n", i+j); 
	//按照补码的形式进行运算,最后格式化成为有符号整数
	return 0;
}

T5

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

T6

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

T7

#include <stdio.h>
unsigned char i = 0;
int main()
{
    for(i = 0;i<=255;i++)
   {
        printf("hello world\n");
   }
    return 0;
}

下面公布答案

1.	a=-1,b=-1,c=255
2.	4294967168
3.	4294967168
4.	-10
5.	死循环 9 8 7 6 5 4... 2 1 0 4294967295 4294967294 
......3 2 1 0 4294967295 4294967294 ...
6.	255
7.	死循环打印hello world!

如果您都对了,那么请再看一下下面的几个变式题

变式1 -改T1

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

变式2 - 改T5

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

下面公布答案

变式1: a=4294967295,b=4294967295,c=255
变式2:5.	死循环 9 8 7 6 5 4... 2 1 0 -1 -2 -3......

2.浮点数存储方式引例

下面请大家再思考一下下面这个问题,这是我们探究剖析浮点数存储方式的一个引例.

int main()
{
	int n = 9;

	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);

	*pFloat = 9.0;
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);
	return 0;
}
答案是:
1.   9
2.   0.000000
3.   1091567616
4.   9.000000
这个现象充分说明了
1.当我们通过浮点数的视角去取出整型变量内的数据时取出来的和其中所存的整型大相径庭.
2.当我们通过整型的视角去取出浮点数变量内的数据时取出来的和其中所存的浮点数类型大相径庭.

也就是说浮点数和整数在内存当中存储的方式是有区别的!!!

1.数据类型详细介绍

1.数据类型介绍

sizeof(char) 	//1
sizeof(short) 	//2
sizeof(int) 	//4
sizeof(long)	//4或者8,不同编译器上可能不同,C语言只规定了sizeof(int) <= sizeof(long)
sizeof(long long)  //8
sizeof(float)		//4
sizeof(double)		//8
char类型范围: 
1.signed char :-128~127
其中二进制序列10000000被规定为-128
2.unsigned char : 0~255

short类型范围:
1.signed short : -32768~32767
2.unsigned short : 0~65535

int 类型
1.signed int : -21亿多~21亿多
2.unsigned int : 0~42亿多 

而数据类型的意义在于:

1.使用这个类型开辟的内存空间的大小
2.如何看待内存空间的视角

这里我们谈一下-128的问题

-128的问题
所谓的数据类型的范围取决于多个比特位能构成的排列组合的个数
比较表面的结论

原码
1111 1111   ->-127  [0-127]
0111 1111   ->+127  [0-127]
我们发现了有两个0出现了,
又因为计算机不会丢弃掉任何一种排列组合问题.因为计算机要用更小的成本去解决更多的数据取值范围问题,
0000 0000 :用来表示0
1000 0000 :只能用来表示128-128,又因为它的最高位是1,所以不能表示128,所以用来表示-128了 

深层次的理解
char c = -128;
printf("%d\n",c);
1 1000 0000  -128原码
1 0111 1111  反码
1 1000 0000  -128补码  
如果你想问为什么这么巧,为什么-128的补码和原码相同,以至于-128的内存当中实际存储的值和我们所多余出来的(因为出现了两个0的问题)1000 0000完全相同.
因为这是非常早期的计算机的科学家借助数学的知识非常巧妙地设计出来的
截断后:1000 0000  char c 补码
0111 1111  反码
0000 0000  原码(这种理解是错误的)
也就是我们无法通过原反补的定义去正确地取出-128

所以-128真实采用的是一种半计算半规定的方式
char [-2^7,2^7-1]
short [-2^15,2^15-1]
int [-2^31,2^31-1]

2. 类型基本归类:

1.整型家族

1.char
关于这里为什么将char字符型数据归为整型家族呢?
这是因为字符在存储时本质上存储的是它的ASCII码值,所以属于整型家族
2.short
3.int
4.long

2.浮点数家族

1.float
2.double

3.构造类型

1.数组类型
注意:
只有当两个数组存储的数据类型和本身的容量大小均相等时才可以说明这两个数组类型相同

int arr[10]的类型是int [10]
int arr[5]的类型是int [5]
所以两者类型不相同

2.结构体类型 struct
3.枚举类型 enum
4.联合类型 union

4.指针类型

int *p1;
char *pc;
float *pf;
void *pv;

4.空类型

void表示空类型
通常应用于函数的返回类型,函数的参数,指针类型

2.整型在内存中的存储:

整数有三种2进制表示方法:原码,反码,补码

原码:
直接将数值按照正负数的形式翻译成二进制即可得到原码

反码
将原码符号位不变,其他位依次按位取反得到反码

补码
反码+1得到补码
对于整形来说:数据存放在内存当中的其实是补码
为什么呢?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;
同时,加法和减法也可以统一处理(CPU只有加法器)
此外,补码与原码相互转换,其运算过程是相同的,(注意:补码的补码就是原码.)
不需要额外的硬件电路。

注意:正数的原/反/补码均相等
负数的原反补码遵循上述规则

3.大小端字节序介绍及判断

低                            高
11       22        33         44      大端字节序存储
44       33        22         11	  小端字节序存储
对于一个整数来说,我们将其转换为16进制的形式,因为16进制看起来更加清晰
不妨有这样一个整型
0x11223344
高位字节  低位字节,从左到右,由高到低
字节序:是以字节为单位讨论存储顺序的  

1.小端字节序存储:把一个数据的低位字节(低权值)的内容存放在低地址处
,把一个数据的高位字节(高权值)的内容存放在高地址处  

2.大端字节序存储:把一个数据的低位字节(低权值)的内容存放在高地址处
,把一个数据的高位字节(高权值)的内容存放在低地址处

3.如果大家觉得有点容易搞混的话,可以记住这么一个口诀
//口诀:对于小端存储而言,权值为比较小,地址数字比较小,所以记为小小小

注意:
1.大/小端存储模式与编译器无关,只与我们的电脑本身的厂商的设计有关
2.只有当某个数据类型占用的字节数超过了1个,它才有大小端模式之分,也就是说char类型是没有大小端之分的

那么如何判断我们的机器采用的是大端存储模式还是小端存储模式呢?

int check_sys()
{
	int a = 1;
	return *(char*)&a;
}

(char*)强制类型转换,  
&a:取出a的第一个地址,&a的类型就是int*类型,所以要强制类型转换,最后*解引用
如果采用小端存储模式
01 00 00 00  //16进制表示
强制转换为char*后是 01
同理,如果采用大端存储模式
00 00 00 01
强制转换为char*后是00

int main()
{
	int result = check_sys();
	if (result == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

4.深度解剖和练习(在这里我们会补充整型提升这个知识点)

看完了上面的知识点后下面我们来一起做一些题目巩固一下
在这里我们先补充一个知识点:整型提升

1.什么是整型提升呢?
表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升.

例如:
char a = 0;
char b = 2;
char c = 5;
a = b + c;
//那么在这里 b和c想要相加,就需要整型提升为普通整型再相加.
//相加完的结果赋值给a,又因为a是char类型,只能存储1个字节,而int类型由4个字节组成,所以数据要发生截断
注意:截断与大小端存储模式无关,截断是在数据的低字节处截断
例如:
int a = 10;
a的补码(因为整型变量在内存中的存储方式是补码,前面提到过):
0000 0000 0000 0000 0000 0000 0000 1010
char b = a;
b的补码:
0000 1010
无论是大端存储还是小端存储,b的截断方式都相同

2.整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行.
CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度.

因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度.
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令).
所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为intunsigned int,然后才能送入CPU去执行运算.

3.那么我们是如何进行整型提升的呢?
整形提升是按照变量的数据类型的符号位来提升的

整形提升的例子:
//负数的整形提升
char c1 = -1;
c1的补码:
(1)111111 这个(1)就是上述说明的变量的数据类型的符号位
因为 char 为有符号的 char
注意:C语言并没有明确规定char单独出现时是signed char 还是unsigned char,但是在VS编译器下char单独出现时默认为signed char

所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
1111 1111 1111 1111 1111 1111 1111 1111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
0000 0001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
0000 0000 0000 0000 0000 0000 0000 0001
//无符号整形提升,高位补0 
unsigned char c3 = -1;
尽管c3的补码:1111 1111,即符号位是1,
但是由于c3是unsigned 类型,所以一律高位补0

其次,我想给大家说一些关于数据存储的理解

请大家告诉我,这样定义一个变量是否正确
unsigned int b = -10;
答案是:正确,但请注意,unsigned修饰b变量后并不是将b直接改为+10,这种想法是非常错误的.
我想告诉大家的是:
1.数字需要带上类型才有意义
2.二进制序列是在读取的时候才有意义,类型决定了如何解释空间内部保存的二进制序列
3.也就是说
变量存的过程:因为内存当中只能存放二进制序列,所以-10在赋给b之前就必须存放到内存当中,而当-10存放到内存当中之后,-10就转化为了补码形式的二进制序列存放在内存当中,然后再赋给b这个变量

存:字面数据必须先转成补码,再放入空间当中.所以,所谓符号位,完全看数据本身是否携带+-.和变量是否有符号无关
变量取的过程:%d %u %c等等的打印方式,看运算后的结果是否重新赋值给了某一个整形变量
取:准确的方式是先看大小端,(只不过因为如果小端存储,那么也会小端读取,不影响用户的使用),取出来之后,再看自身类型然后在决定要不要看最高符号位,如果不需要,直接二进制转成10进制,如果需要,则需要转成原码,然后才能识别,

取的过程就相当于我说:我手里有100,请问我有多少有多少价值的金钱?
注意,我只说我有100,可是我没有说我是有100人民币,还是100欧元,英镑,等等,也就是说100这个数字如果没有赋予它类型,那么这个数字是没有任何意义的.

也就相当于我上面说的取的含义

第一题:

#include <stdio.h>
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.写出int a的原,,2.看看是否要发生截断和整型提升
3.注意整型数据在打印时打印的是原码!!!!!!!
4.这些加减计算的本质是:
两个数据在内存级别当中(补码)进行二进制的相加
a的原码:1000 0000 0000 0000 0000 0000 0000 0001
a的反码:1111 1111 1111 1111 1111 1111 1111 1110
a的补码:1111 1111 1111 1111 1111 1111 1111 1111

因为a是char类型所以要发生截断
注意:截断一定是截的补码,得到的也是补码,(因为整型变量在内存中的存储方式是补码,前面提到过)
截断后:a补码:11111111
因为要用%d(10进制有符号int)的方式打印,也就是我们要发生整型提升
整型提升后依然是补码:
1111 1111 1111 1111 1111 1111 1111 1111:这是整型提升后的补码
1111 1111 1111 1111 1111 1111 1111 1110:				 反码
1000 0000 0000 0000 0000 0000 0000 0001:				 原码
又因为我们是以%d有符号类型去打印,所以结果为-1

同理:因为在VS编译器下:char单独出现时代表signed char,所以说a等于-1,那么b也等于-1

那我们看c呢?
前面提到过当发生整型提升时,如果对应的变量为unsigned 类型,则不管它具体的符号位是0还是1,一律提升0
所以整型提升后的补码:
0000 0000 0000 0000 0000 0000 1111 1111:这是整型提升后的补码
注意因为c是unsigned类型,所以c同时也是非负数,所以c的原,,补码均相等,所以整型提升后的原码是
0000 0000 0000 0000 0000 0000 1111 1111:转换为10进制后为255

而在变式题1中我们用%u打印a,b,c
则对于a和b:整型提升后补码,原码,反码均相等,也就是说直接打印
1111 1111 1111 1111 1111 1111 1111 1111
转换为10进制后为4294967295,unsigned int的最大值,42亿之大

又因为c本身就是unsigned 类型,所以c为非负整型,%d打印和用%u打印结果完全相同

第二题:

#include <stdio.h>
int main()
{
    char a = -128;
    printf("%u\n",a);
    return 0;
}
跟上一个题是同一种思路,我们一起看一下
1000 0000 0000 0000 0000 0000 1000 0000  //-128 原码
1111 1111 1111 1111 1111 1111 0111 1111  //-128 反码
1111 1111 1111 1111 1111 1111 1000 0000  //-128 补码
截断:
1000 0000 char a 补码
整型提升
1111 1111 1111 1111 1111 1111 1000 0000  整型提升后的补码==原码==反码
所以直接打印得到10进制结果(注意是%u类型的方式):4294967168

讲了这么多,下面请大家思考一下下面这个问题,注意和第二题的方法完全相同

第三题

#include <stdio.h>
int main()
{
    char a = 128;
    printf("%u\n",a);
    return 0;
}
a为正数,所以a的补码,原码,反码均相同
0000 0000 0000 0000 0000 0000 1000 0000
截断
1000 0000
大家可以看到这个题中截断后仍为1000 0000,所以这个题跟上一个题的答案一样.
如果大家做对了,恭喜你掌握了这个方法
如果没有做对,不要灰心,总结一下,一定能熟练掌握的

第四题:

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

注意:正因为减法运算a-b需要转换为a+(-b)
但是只靠原码来运算是无法运算出正确的结果的,所以引入了补码这个概念,也就是说在计算机中加减运算都是对补码之间进行的运算,
注意:补码加补码==补码

1000 0000 0000 0000 0000 0000 0001 0100  i的原码
1111 1111 1111 1111 1111 1111 1110 1011  i的反码
1111 1111 1111 1111 1111 1111 1110 1100  i的补码
0000 0000 0000 0000 0000 0000 0000 1010  j的原码/补码/反码
1111 1111 1111 1111 1111 1111 1111 0110  i+j得到的结果的补码
又因为我们要用%d的方式去打印,也就是用signed int 的方式去解释上面的那一串二进制序列
1111 1111 1111 1111 1111 1111 1111 0101   i+j的反码
1000 0000 0000 0000 0000 0000 0000 1010  i+j的原码即-10
所以答案为-10

第五题:

int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}
}
这里,for循环的判断条件中我们想要判断i和0的大小关系,就必须要将i从内存当中取出来再跟0进行比较,而我们取的方式就是unsigned int类型,所以i永远大于等于0,所以会死循环,打印9 8 7 6 ..... 3 2 1 0 4294967295 4294967294.........而且甚至当这个有42亿之大的数减为0后又变为了4294967295 继续递减永远不停
前面提到过-1%u打印就是4294967295

所以在我们日后的学习生涯,职业生涯当中,尽量不要用unsigned 类型的整数来作为循环变量,稍有不慎,便极有可能发生死循环的现象

变式2:那么如果我们将%u改为%d呢?那么它会打印
9 8 7 6 .... 3 2 1 0 -1 -2 -3 .....

T6

int main()
{
    char a[1000];
    int i;
    for(i=0; i<1000; i++)
   {
        a[i] = -1-i;
   }
    printf("%d",strlen(a));
    return 0;
}
这题就有点坑人了,很有可能会误认为答案是1000
但是我们要注意到
1.a数组的数据类型是char类型,只占一个字节.
而赋值操作中放入的是-1~-1000的数字,
超出了char类型的范围,所以会发生截断

2.'\0'的ASCII码值是数字0
(接下来对于'\0'的理解与这个题没有多大的关系,只是想给大家补充一些大家可能没有留心关注的一些知识)
对于'\0'的理解:'\0'不属于字符串的内容部分,而属于字符串的结束分隔符,
这个'\0'并没有数据层面上的含义,它仅仅是告诉相关函数或者程序我的字符串到这里就结束了,
但是'\0'也需要占用一个字节的空间

3.发生截断,也就是到了-128,10000000(原码)
int 类型的-129:原码: 1000 0000 0000 0000 0000 0000 1000 0001
补码:				1111 1111 1111 1111 1111 1111 0111 1111
截断:01111111(补码)正数==原码==反码
在这里-1+(-128)已经越界(超出数据范围),所以变成了正数是很正常的
即+127所以实际存的是

-1 -2 -3 .... -127 -128 127

int -130:原码:		1000 0000 0000 0000 0000 0000 1000 0010
补码:				1111 1111 1111 1111 1111 1111 0111 1110
截断:01111110(补码)正数==原码==反码
即+126

所以实际存的是

-1 -2 -3 .... -127 -128 127 126 125.....3 2 1 0 -1.....循环下去

4.strlen返回'\0'之前的字符个数,
又因为在前面我提到过char类型在内存当中实际上存放的是该字符所对应的ASCII码值,
也就是说strlen函数在判断一个字符是否为0,判断的是它的ASCII码值,
注意:里面存的是数字-1 -2 -3 .....-127 -128 127 126 .....3 2 1 0 -1 ....
一直往复循环直到存够1000个数据
而不是存'-1' '-2' ....,
其中字符'0'的ASCII码值为48,
数字0"ASCII"值为0

T7

#include <stdio.h>
unsigned char i = 0;
int main()
{
    for(i = 0;i<=255;i++)
   {
        printf("hello world\n");
   }
    return 0;
}
这题就很简单了,因为unsigned char类型的最大值为255,所以循环条件一直满足,所以发生死循环

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

1.国际标准IEEE 754

要想搞清楚上面那个现象的原因,首先我们先要搞清楚浮点数在内存当中是如何存储的?

首先,我们先看一个国际标准

根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
注意:在这里^我们规定为次方,而不是按位异或操作符
(-1)^S * M * 2^E
(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。

下面我们举一个例子:
浮点数:  5.5  ->  10进制
二进制:  101.1  -> 1.011 * 2^2 ->(-1)^0 * 1.011 * 2^2
其中: 								S:0   M:1.011   E:2
从中我们可以看出,
要想唯一确定一个浮点数,只需要确定它的S,M,E这三个值即可,
也就是说,我们如果想要存储一个二进制浮点数,只需要存储它的S,M,E这三个值即可

2.具体存储方式(float,double)

其次,我们在上面已经知道了单精度浮点数:float占的字节数为4,
双精度浮点数double占的字节数为8
那么它们具体的存储方式和区别又在哪里呢?

1.对于32位的浮点数,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M.
2.对于64位的浮点数,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M.
IEEE 754对于有效数字M和指数E,还有一些特别规定:
1.前面我们说过 1<= M <=2,
也就是说,M可以写成1.xxxxxx的形式,其中xxxxxx表示小数部分

2.所以IEEE 754规定在计算机内部保存M时,默认这个数的第一位总是1,
因此可以被舍去,只保存后面的xxxxxx部分.

比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去.这样做的目的,是节省1位有效数字.32位浮点数为例,留给M只有23位,
将第一位的1舍去以后,等于可以保存24位有效数字

3.至于指数E,情况就比较复杂。

首先,E为一个无符号整数(unsigned int)
这意味着,如果E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047.但是,我们知道,科学计数法中的E是可以出现负数的,
所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,
对于8位的E,这个中间数是127;
对于11位的E,这个中间数是1023.

比如,
十进制 0.5
二进制 0.1  -> 1.0 * 2^(-1) -> S=0 M=1.0 E=-1
                                     对于float:-1+127=126
                                     对于double:-1+1023=1022 

3.实例

下面我们看一个实例

int main()
{
	float f = 5.5f;
	//101.1
	//1.011 * 2^2
	//S=0  M=1.011 E=2 (公式中的)
	//S=0  M=011   E=2+127 (实际内存中存储的)

	//0100 0000 1011 0000 0000 0000 0000 0000
//16进制:4  0    b    0   0    0    0     0
//40 b0 00 00
	return 0;
}

接下来我们通过调试看一下f在内存中的存储
在这里插入图片描述
我这台电脑是小端存储模式,所以第一行显示的是 00 00 b0 40
,同时说明了浮点数在内存中的存储方式也是有大小端之分的.

4.取出浮点数

下面,我们说一下指数E是如何从内存中取出的

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

这是刚才的那个实例
int main()
{
	float f = 5.5f;
	//101.1
	//1.011 * 2^2
	//S=0  M=1.011 E=2 (公式中的)
	//S=0  M=011   E=2+127 (实际内存中存储的)
	  
	//0     10000001  011 0000 0000 0000 0000 0000
	  S        E(129)   M

	//0100 0000 1011 0000 0000 0000 0000 0000
//16进制:4  0    b    0   0    0    0     0
//40b00000
	//10000001
	   E存(129)
	//00000010
	  E取(2)
	 //011
	   M存
	 //1011
	  M取
	 //0
	  S存
	 //0
	 S取
	 所以我们可以从中看出M取,E取,
	 S取均与IEEE 754标准提供的"公式"相符合,成功取出
	return 0;
}
2.E全为0
也就是说0-127后得到的公式中的E:-127,所以原始数据的数量级为2^-127232次方就已经42亿多了,所以原始数据趋近于0

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

3.E全为1
也就是说E-127=128,又因为2^128非常非常大,所以可以认为原始数据趋近于无穷大

所以IEEE 754这么规定:
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);

5.解释引例

int main()
{
	int n = 9;

	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);//9
	//0000 0000 0000 0000 0000 0000 0000 1001  n在内存中的存储,用整型的方式去存
	//接下来用浮点数去取
	//S=0   E=0   M=000 0000 0000 0000 0000 1001(存储)
	//S=0   E=-126 M=0.00 0000 0000 0000 0000 1001
	//即:0.00 0000 0000 0000 0000 1001 * 2^-126  趋近于无穷小(公式)
	//因为E=0,所以符合上面所讲的取出浮点数的第二种特例:即E=0,此时表明原数据为无穷小,所以打印出0.000000
	printf("*pFloat的值为:%f\n", *pFloat);

	*pFloat = 9.0;
	//用浮点数存储
	//10进制: 9.0
	//二进制: 1001.0   -> (-1)^0 * 1.001 *2^3
	//S=0  E=3  M=1.001 (公式)
	//S=0  E=3+127  M=001(存储)

	//0    10000010   00100000000000000000000   
	//S      E          M
	//0100 0001 0001 0000 0000 0000 0000 0000 
	//用整数去取:
	//因为是正数:所以原码==反码==补码
	//转化为10进制为1091567616
	printf("n的值为:%d\n", n);
	printf("*pFloat的值为:%f\n", *pFloat);//9.0
	return 0;
}

以上就是我们今天所分享的全部内容,希望能对大家有所帮助!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

program-learner

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值