当前位置:   article > 正文

C语言之数据在计算机内部的存储

C语言之数据在计算机内部的存储

一、前言

  • 前面我们已经学习了基本的内置类型:
  • 以及他们所占存储空间的大小~~

在这里插入图片描述

类型的意义:

  1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)
  2. 如何看待内存空间的视角

二、类型的基本归类

1、整型家族

  • 下面是整形家族

在这里插入图片描述

  • char在字符存储的时候存的是一个ASCLL码值,而ASCLL码值是一个整数

  • 数值有正数和负数之分

    • 有些数值只有正数,没有负数(身高)—— unsigned
    • 有些数值,有正数也有负数(温度)—— signed

2、浮点数家族

  • 浮点数只分为两类,一个是【float】,一个是【double】

在这里插入图片描述

3、构造类型

在这里插入图片描述

4、指针类型

在这里插入图片描述

-下面我们来介绍一下这个空指针

  • void*叫做【空指针】
    • 对于int类型的指针可以用来接收int类型的数据的地址
    • 对于char类型的指针可以用来接收char类型的数据的地址
    • 对于float类型的指针可以用来接收float类型的数据的地址
  • 对于void类型的指针可以用来接收任何类型数据的地址【它就像一个垃圾桶一样,起到临时存放的作用】

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

三、整型在内存中的存储

1、原码、反码、补码

1.1 概念

计算机中的整数有三种2进制表示方法,即原码、反码和补码。

  • 三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位正数的原、反、补码都相同,负整数的三种表示方法各不相同
int a = 10;
  • 1
  • 对于正数说,因为原、反、补都是相同的~~
    在这里插入图片描述

int a = -10;
  • 1
  • 对于负数来说就不太一样了,要得到反码就将原码除符号位外其余各位取反,要得到补码的话就在反码的基础上 + 1

在这里插入图片描述

1.2 原码与补码的转换形式
  • 原码到补码 —— 1种方式
  1. 原码取反,+1得到补码
  • 补码到原码 —— 2种方式
  1. 补码 - 1,取反得到原码
  2. 补码取反,+1得到原码

在这里插入图片描述

1.3 计算机内部的存储编码
  • 对于整形来说:数据存放内存中存放的是补码。

在这里插入图片描述

  • 这里存储的是16进制类型的补码~~
  • 在【进制转换】中,4位二进制表示1位16进制。通过将补码4位4位进行一个划分就可以得出8个16进制的数字为ff ff ff f6,这里发现是倒着存的,那么就要涉及到大小端存储的~~

2、大小端介绍~~

2.1 为什么要有大端和小端之分?

在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit

  • 但是在C语言中除了8 bit的char之外,还有16 bit的short 型,32 bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32 位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因 此就导致了大端存储模式和小端存储模式。
  • 例如:一个 16bit 的 short 型 x ,在内存中的地址为 0x0010 , x 的值为 0x1122 ,那么 0x11 为 高字节, 0x22 为低字节。对于大端模式,就将 0x11 放在低地址中,即 0x0010 中, 0x22 放在高 地址中,即 0x0011 中。小端模式,刚好相反。我们常用的 X86 结构是小端模式,而 KEIL C51 则 为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式 还是小端模式。大端】和【小端】的由来,接下去呢就正式地来给读者介绍一下这种倒着存放的方式
2.2 大(小)端字节序存储
  • 【大端(存储)模式】:是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;
  • 【小端(存储)模式】:是指数据的低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中;

  • 对于下面这一个十六进制数0x11223344,以进制的权重来看的话右边的权重低【0】,左边的权重高【3】,所以11为高位,44为低位。若是对其进行小端字节存储的话就要将44存放到低位,11存放到高位

在这里插入图片描述

  • 【大端字节序存储】是要将高位存放到低地址,低位存放到高地址,因此11要放在左边,44要放在右边

在这里插入图片描述

2.3 一道百度系统工程师笔试题

请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(10分)

  • 下面我通过一个简单的数作为案例来进行一个分析
int a = 1;
  • 1

在这里插入图片描述

  • 对于a以【小端字节序存储】会将01放在低位;而以【大端字节序存储】会将01放在高位,那么此时我们只需要获取到内存中规定最低位即可
  • 因为01在内存中表示一个字节,而一个【char】类型的数据就为1个字节,所以此时我们可以使用到一个字符型指针接受要存放到内存中的这个数,然后对其进行一个解引用,就可以获取到低位的第一个字节了
char* p = &a;
  • 1
  • 若直接使用一个字符型指针去接收一个整型数值的地址,就会出现问题,因为一个字符型的指针只能放得下一个字节的数据,所以我们要对这个整型的数值去进行一个强制类型转换为字符型的地址

在这里插入图片描述

  • 通过强制类型转换后,再对这个字符型指针进行解引用,就可以取到一个字节的数据,继而对其进行一个判断,如果为1的话那就是【小端】,反之则是【大端】
char* p = (char *)&a;
if (*p == 1){
	printf("小端\n");
}
else {
	printf("大端\n");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里插入图片描述


  • 通过学习了函数章节,我们可以对其进行一个分装~~
int check_sys(int num)
{
	char* p = (char*)#
	if (*p == 1) {
		return 1;
	}
	else {
		return 0;
	}
}
int ret = check_sys(1);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

  • 或者,对于这个if判断我们可以就直接写成解引用的形式,然后对其去进行一个判断
if (*(char*)&num == 1)
  • 1

  • 那既然是我们要return 1或者0的时候,其实在解引用获取到低地址的第一个字节时直接return即可~~
int check_sys(int num){
	return *(char*)#
}
  • 1
  • 2
  • 3

3、数据范围的介绍

3.1 char与signed char数据范围
  • 首先我们通过下面这幅图来看一看对于有符号的char和无符号的char

    在内存中所能表示的范围各自是多少:

    • 【signed char】:-128 ~ 127
    • 【unsigned char】:0 ~ 255

在这里插入图片描述

  • char数据类型在内存中占1个字节,也就是8个比特位。若是从00000000开始存放,每次+1上去然后逢二进一,之后你就可以得出最大能表示的正整数为【127】,可是在继续+1后又会进行进位然后变为10000000,符号位为1,这是-128这是为什么呢?

    在这里插入图片描述

  • 在内存中都是以【补码】的形式进行存放,所以我们看到的1 1111111只不过是补码的形式

  • 但是对于10000000我们直接将其记作【-128】,它就对应的【-128】在内存中的补码,通过去写出【-128】的原、反、补码可以发现是需要9个比特位来进行存放,对于char类型的数值而言只能存放8个比特位,因此在转换为补码之后会进行一个截断

  • 最后剩下的就是10000000,即为有符号char的最小负数为【-128】

3.2 unsigned char数据范围
  • 因为是无符号char,所以第一位不作为符号位,就是从0 ~ 255

4、笔试题

  • 有符号的数在整型提升的时候补符号位,无符号的数在整型提升的时候补0
  1. %u 是打印无符号整型,认为内存中存放的补码对应的是一个无符号数
  2. %d 是打印有符号整型,认为内存中存放的补码对应的是一个有符号数

第一道

#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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 我们来分析一下,可以看到【a】和【b】都是有符号位的char类型,那它们就是一样的,现在将-1存放到这两个数据中去,首先你应该要考虑到的是一个数据放到内存中去是以补码的形式
  • 所以我们首先将-1转换为补码的形式
1 0000000 00000000 00000000 00000001
1 1111111 11111111 11111111 11111110
1 1111111 11111111 11111111 11111111
  • 1
  • 2
  • 3
  • 可是呢,需要存放的地方又是char类型的变量,只能存放8个字节,无法放得下这32个字节,因此便需要进行一个截断的操作,放到变量a和变量b中都只剩下11111111这8个字节。
  • 对于变量c来说,它是一个无符号的char类型变量,不过-1存放到它里面还是11111111这8个字节不会改变,只不过在内存中的变化与有符号char不太一样~~
printf("a = %d, b = %d, c = %d", a, b, c);
  • 1
  • %d的形式进行一个打印,但是呢三个变量所存放的都是char类型的变量,因此会进行一个整型提升,只是有符号数的整型提升和无符号数不太一样
//a b - 有符号数
11111111111111111111111111111111 - 补符号位

//c - 无符号数
00000000000000000000000011111111 -0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 在进行整型提升之后,这些二进制数据还是存放在内存中的,可是要输出打印在屏幕上的话还要转换为【原码】的形式
11111111111111111111111111111111
10000000000000000000000000000000
10000000000000000000000000000001 ——>-100000000000000000000000011111111 ——>255
  • 1
  • 2
  • 3
  • 4
  • 5

第二道

#include <stdio.h>
int main()
{
    char a = -128;
    printf("%u\n",a);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 同理,一个整数存放到内存中,首先要将其转换为【补码】的方式
10000000 00000000 00000000 10000000
11111111 11111111 11111111 01111111
11111111 11111111 11111111 10000000
  • 1
  • 2
  • 3
  • 接着因为这32个二进制位要存放到一个char类型的变量中,因为进行截断10000000
  • 然后在内存中需要进行一个整型提升,char类型的变量将会填充符号位11111111111111111111111110000000
  • 执行打印语句,可以看到这里是以%u的形式进行打印,认为在内存中存放的是一个无符号整数。我们知道,对于无符号整数来说,不存在负数,所以其原、反、补码都是一样的,因此在打印的时候就直接将其转换为十进制进行输出
printf("%u\n",a);
  • 1

输出:

在这里插入图片描述

  • 不信的话还可以使用计算器来算一下

在这里插入图片描述


第三道

#include <stdio.h>
int main()
{
    char a = 128;
    printf("%u\n",a);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 接下去我们来看第三道题,可以看出和上面那题基本基本一样,只是把-128变成了128而已
  • 如果是【128】的话放到内存中就不需要像负数那样还要进行很多的转化了,因为正数的原、反、补码都一致,当我们内存中真正存的是10000000又因为是%u的形式打印,然后需要整形提升
00000000 00000000 00000000 10000000
  • 1
  • 然后我们可以看到时10000000符号位是1,所以整形提升的时候补1
11111111 11111111 11111111 10000000
  • 1
  • 所以还是和上题一样,这里就不多赘述~~

第四道

int main()
{
	int i = -20;
	unsigned int j = 10;
	printf("%d\n", i + j);
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

下面是解法:

int main()
{
	int i = -20;
	//1 0000000 00000000 00000000 00010100
	//1 1111111 11111111 11111111 11101011
	//1 1111111 11111111 11111111 11101100

	unsigned int j = 10;
	//0 0000000 00000000 00000000 00001010
	printf("%d\n", i + j);

	//1 1111111 11111111 11111111 11101100
	//0 0000000 00000000 00000000 00001010
  //------------------------------------------
	//1 1111111 11111111 11111111 11110110
	
	//1 1111111 11111111 11111111 11110110
	//1 0000000 00000000 00000000 00001001
	//1 0000000 00000000 00000000 00001010 —— 【-10】

	//按照补码的形式进行运算,最后格式化成为有符号整数
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 本次我们用到的是两个int类型的数据,一个是有符号的,一个是无符号的。但无论是有符号还是无符号,放到内存中都是要转换为补码的形式
  • 就是对算出来的两个补码一个二进制数的相加运算,注意这里是将整数存放到int类型的变量中去,所以不需要进行【截断】和【整型提升】
   1 1111111 11111111 11111111 11101100
   0 0000000 00000000 00000000 00001010
------------------------------------------
   1 1111111 11111111 11111111 11110110
  • 1
  • 2
  • 3
  • 4
  • 在运算之后要以%d的形式进行打印输出,那就会将内部中存放的补码看做是一个有符号数,既然是有符号数的话就存正负,可以很明显地看到最前面的一个数字是1,所以是负数,要转换为原码的形式进行输出
1 1111111 11111111 11111111 11110110
1 0000000 00000000 00000000 00001001
1 0000000 00000000 00000000 00001010 —— 【-10
  • 1
  • 2
  • 3

在这里插入图片描述


第五道

  • 接下去第五道,是一个for循环的打印
int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
	}
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

运行结果:>死循环

  • 有同学就很诧异为什么会陷入死循环呢?这不是就是一个正常的打印过程吗?

  • 其实,问题就出在这个unsigned,把它去掉之后就可以正常打印了

  • 回忆一下我们在将无符号整数的时它的数据范围是多少呢

    • 对于char类型来说是0 ~ 255;
    • 对于short来说是0 ~ 65536;
    • 对于int类型来说是0 ~ 16,777,215
  • 对比进行观察其实可以发现它们的数值范围都是 > 0的,所以对于无符号整数来说就不会存在负数的情况。因此这个for循环的条件【i >= 0】其实是恒成立的,若是当i == 0再去--,此时就会变成【-1】

  • 对于【-1】我们有看过它在内存中的补码形式为11...11是全部都是1,而此时这这个变量i又是个无符号的整型,所以不存在符号位这一说,那么在计算机看来它就是一个很大的无符号整数。此时当i以这个数值再次进入循环的时候,继续进行打印,然后执行--i,最后知道其为0的时候又变成了-1,然后继续进入循环。。。

光是这么说说太抽象了,我们可以通过Sleep()函数在打印完每个数之后停一会,来观察一下

#include <windows.h>

int main()
{
	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
		Sleep(200);
	}
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 接着你便可以发现,当i循环到0的时候,突然就变成了一个很大的数字,这也就是印证了我上面的说法

在这里插入图片描述


第六道

  • 本题和四五道的原理是一样的,对于unsigned char来说,最大的整数范围不能超过255,所以当这里的【i】加到255之后又会再+1就会变成00000000,此时又会进入循环从0开始,也就造成了死循环的结果
unsigned char i = 0;
int main()
{
	for (i = 0; i <= 255; i++)
	{
		printf("hello world\n");
	}
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在这里插入图片描述


第七道

  • 最后一道,我们来做做综合一些的,涉及到字符串函数strlen
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 首先来看函数主体,也是一个for循环,在开头定义了一个char类型的数组,大小为1000,。在循环内部呢对它们进行一个初始化,那最后里面的数字一定是
-1  -2  -3  -4  -5  -6  -7 ...
  • 1
  • 但是呢,我们在上面学习过的有符号signed char,它和char是一样的,数据的范围在【-128 ~ 127】,所以当i加到128的时候,这个位置上的值变为【-129】,此时在计算机内部会将它识别成【127】,同理【-130】会被识别成为【126】。。。依次类推,最后当这个值为【0】的时候若再去减就会变成【-1】,然后又变成【-2】【-3】【-4】。。。一直当这个i累加到1001的时候截止

  • 我们要通过strlen()去求这个数字的长度,对于strlen()来说,求的长度是到\0截止,那也就是上面的【0】,不需要去关心后面的第二、三轮回

  • 那其实这也就转变成了让我们去求有符号char在内存中可以存储多少个。这很简单,范围是-128 ~ +127,二者的绝对值一加便知为255

//-1  -2  -3  -4  -5  -6  -7 ...-128  127  126  ...  0 -1 -2 -3....
printf("%d", strlen(a));
  • 1
  • 2
  • 来看一下运行结果~~

在这里插入图片描述

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

  • 在第二模块,我有提到过一个叫做【浮点数家族】,里面包含了[float][double]类型,对于浮点数其实我们不陌生,在上小学的时候就有接触到的3.14圆周率,还有以科学计数法表示的1E10

  • 在计算机中整型类型的取值范围限定在:limits.h;浮点型类型的取值范围限定在:float.h

  • 我们可以在【everything】中找找有关float.h这个头文件

在这里插入图片描述

1、引入

  • 首先要了解浮点数在内存中的存储规则,我们要通过一个案例来进行引入。请问下面四个输出语句分别会打印什么内容?
int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);		//1
	printf("*pFloat的值为:%f\n", *pFloat);		//2
	*pFloat = 9.0;
	printf("num的值为:%d\n", n);	//3
	printf("*pFloat的值为:%f\n", *pFloat);		//4

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 来简单分析一下,整型变量n里面存放了9,然后通过强制类型转换为浮点型的指针,存放到pFloat中。
    • 首先第一个去打印n的值毫无疑问就是9
    • 第二个对pFloat进行解引用访问,对于float类型的指针与int一样都可以访问四个字节的地址,所以解引用便访问到了n中的内容,又因为浮点数小数点后仅6位有效,因此打印出来应该是9.000000
    • 接下去通过pFloat的解引用修改了n的值,不过第三个以%d的形式进行打印,应该也还是9
    • 第四个的话也是以浮点数的形式进行打印,那应该也是9.000000
  • 可结果真的和我们想象的一样吗?让我们来看一下~~

在这里插入图片描述

  • 可以看到,我们猜测推理的4个里面对了两个,中间的两个出了问题,而且还是两个很古怪的数字,对于n*pFloat 在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大?

这要涉及到浮点数在内存中【存】与【取】规则,接下去我们首先来了解一下这个规则~~

2、浮点数存储规则

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

V = (-1)^S * M * 2^E

  • (-1)^S表示符号位,占一位。当S = 0,V为正数;当S = 1,V为负数
  • M【尾数】表示有效数字,放在最低部分,占用23位(1 <= M < 2
  • 2^E表示指数位。E为指数,占用8位【阶符采用隐含方式,即采用移码方法来表示正负指数】

  1. v = 5.5
    • 整数部分的5可以写成101,这毋庸置疑,但是这个小数部分的5要如何去进行转换呢?对于0.5来说我们刚才看了小数部分的权重之后知道是2-1,所以直接使这一位为1即可
    • 接着我们就要去求出S、M和E,对于M来说是>= 1并且< 2的,不过这里的101.1却远远大于1,所以我们可以通过在操作符中学习的【移位】操作将这个数进行左移两位,但是左移之后又要保持与原来的数相同,所以可以再乘上22*使得与原来的二进制相同。接着根据公式就可以写出*(-1)0 * 1.011 * 22这个式子,然后可以得出S、M、E为多少了
  2. v = -5.5
    • 这个和上面的一样,就是前面加了一个负号,那只是符号位进行了修改的话我们只需要变动S就可以了,即S == 1
  3. v = 9.5
    • 如果知道了第一题如何计算的话这题也是同样的道理,只是整数部分发生了一个变化而已
  • 可并不是所有数的小数位都是0.5,如果是0.25的话你可以将【2-2】置为1,依次类推。。。可以对于下面这个3.3里面的0.3你会如何去凑数呢,首先0.5肯定不能,那只能有0.25,但若是再加上0.125的话那就多出来了,那应该配几呢?
  • 其实你将后面的数一个个地去列出来就可以发现是没有数字可以配成0.3的。所以我们可以得出这个数字其实是无法在内存中进行保存。这也是为什么浮点数在内存中容易发生精度丢失的原因

进一步探索指数E与尾数M的特性:

IEEE 754标准规定:

  • 对于32位的浮点数【float】,最高的1位是符号位S,接着的8位是指数E,剩下的23位为有效数字M

在这里插入图片描述

  • 对于64位的浮点数【double】,最高的1位是符号位S,接着的11位是指数E,剩下的52位为有效数字M

在这里插入图片描述

IEEE 754对有效数字M和指数E,还有一些特别规定:

首先是对于有效数字(尾数)M

  • 前面说过 1 ≤ M < 2 ,也就是说,M可以写成 1.xxxxxx 的形式,其中xxxxxx表示小数部分
  • IEEE 754规定,在计算机内部保存M时,默认这个数的第一位总是1因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。
  • 以32位浮点数为例,留给M只有22位,但若是将第一位的1舍去以后,等于可以保存23位有效数字,精度相当于又高了一位

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

  • 首先,E为一个无符号整数(unsigned int

  • 那对于无符号整数来说,我们在上面有介绍过,如果E为8位,它的取值范围为[0 - 255];如果E为11位,它的取值范围为[0 - 2047]

  • 但是,我们知道,科学计数法中的E是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数对于8位的E,这个中间数是127;对于11位的E,这个中间数是1023

  • 简单一点,还是上面讲到过的5.5,注意如果定义成【float】类型的变量的话要在后面加上一个f作为和【double】类型的区分

float f = 5.5f;
  • 1
  • 然后我们对5.5这个数字去进行分析它存入到内存中的样子。通过上面算出来的
 S = 0, M = 1.011, E = 2
  • 1

去写出这32位浮点数存放到内存中是一个怎样的形式

  • 对于符号数S来说就是把0存进去,占一位

  • 对于指数E来说为2,32位浮点数要加上一个中间值127,所以要存入的十进制数为129,再将其转换为8位二进制即为10000001

  • 对于尾数M来说,需要舍去整数位1,然后将【小数部分】的011这三位放到内存中,但是规定了M为23位,此时我们只需要在后面补上20个0即可

  • 然后便可以对这个32个比特位进行划分,8位一个字节,得出40 b0 00 00

  • 还有一点莫要忘了!还记得我们上面讲到的【大小端】存放吗?要存放到内存中的最后一步就是将其进行小端存放【这是我的机器】,即为00 00 b0 40


如何将浮点数从内存中【读取】出来呢?

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

1. E不全为0或不全为1

  • 对于这种情况就是最普通的,若是E存放在内存中的8位二进制数不全为0或者不全为1的话,那么直接按照上面说到过多一些M与E【写入内存】的规则进行一个逆推即可
  • 以32位浮点数为例,因为我们在计算指数E的时候加上了一个127,那么此时减去127即可;在计算尾数M的时候舍去了整数部分的1,那次此时再补上这个1即可

2. E全为0

  • 对于E全0的这种情况很特殊,也就是意味着8位二进制全为0即00000000,这个情况是在指数E加上127之后的结果,那么原先最初的指数是多少呢?那也只能是-127了。那如果这个指数是-127的话也就相当于是【1.xxxx * 2-127】,是一个非常小的数字,几乎是和0没有什么差别
  • 这时,浮点数的指数E等于1-127(或者1-1023)即为真实值;对于尾数M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于0的很小的数字

3. E全为1

  • 最后一种就是当E全为1的时候即11111111,这个情况也是在指数E加上127之后的结果,那么原先最初的指数是多少呢?那便能是128了,那也只能是-127了。那如果这个指数是-127的话也就相当于是【1.xxxx * 2128】,是一个非常大的数字
  • 这时,如果尾数M全为0,表示±无穷大(正负取决于符号位s);

以上就是有关浮点数如何【写入】内存和从内存中【读取】的所有相关知识


3、开局疑难解答

int main()
{
	int n = 9;
	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);		//1
	printf("*pFloat的值为:%f\n", *pFloat);		//2
	*pFloat = 9.0;
	printf("num的值为:%d\n", n);	//3
	printf("*pFloat的值为:%f\n", *pFloat);		//4

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 首先写出【n = 9】在内存中的补码0 0000000 00000000 00000000 00001001。然后是将这个n的地址存放到了一个浮点型的指针中去
int n = 9;
float* pFloat = (float*)&n;
  • 1
  • 2
  • 那么此时进行一个打印,以%d的形式打印n不用考虑就是9;但是后一个就不一样了,对浮点型的指针进行解引用,那也就是要将存放在内存中的浮点数进行读取出来
printf("n的值为:%d\n", n);
printf("*pFloat的值为:%f\n", *pFloat);
  • 1
  • 2
  • 那编译器此时就会站在浮点数的角度去看待这个00000000000000000000000000001001,将第一位看做是符号位0,即S == 0,然后接下去就是8个比特位E,不过可以发现这个E是全0呀00000000,就是我们上面所讲到的这种特殊情况
  • 那此时后面的尾数M就不可以添上前面的整数部分1了,而是应该写成0.xxxxxx的形式即0.000000000000000000001001。那对于指数E应该等于1-127为【-126】。所以最后写出来v的形式为
(-1)^0 * 0.000000000000000000001001 * 2^ (-126)
  • 1
  • 此时我们再去计算这个值打印的结果是多少,其实完全不需要计算,仔细观察就可以发现S为1,M为一个很小的数,若E再去乘上这个很小的数那只会更小,然后无限接近0,。那最后根据这个浮点数小数点后6位有效,便可以看出最终的结果为0.000000

  • 然后我们再来看下面的。此时pFloat进行解引用,然后将9.0存放到n这块地址中去,那也就相当于是我们最先学习了如何将一个浮点数存放到内存中去
*pFloat = 9.0;
  • 1
  • 那我们可以很快将其转换为二进制的形式1001.0,然后通过v的公式得出(-1)^0 * 1.001 * 2^3
  • 此时再去将其转换为IEEE 754标准规定的32位浮点数。首先看到指数E为3,加上127之后为130,那么二进制形式即为10000010,尾数M也是同理,舍去1后看到001,后面添上20个0补齐23个尾数位。最后的结果即为
——> 0 10000010 00100000000000000000000
  • 1
  • 然后去执行打印语句,那我们以浮点数的形式放进去,但是以%d的形式打印n,那么这一串二进制就会被编译器看做是补码,既然是打印就得是原码的形式,不过看到这个符号位为0,那我们也不需要去做一个转换,它就是原码
printf("num的值为:%d\n", n);
  • 1
  • 那么最后机器就会将二进制形式的原码转换为十进制的形式然后打印。一样,我们可以将它放到【程序员】计计算器进行运行,然后找到十进制的形式,便是最后打印输出在屏幕上的结果
01000001000100000000000000000000 —— 1,091,567,616
  • 1

好了,本文到这里就结束了,感谢大家的收看

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/393857
推荐阅读
相关标签