0
点赞
收藏
分享

微信扫一扫

第十五届蓝桥杯省赛大学B组(c++)

ixiaoyang8 2024-05-10 阅读 20

大家可能在学习的时候会经常疑惑数据在内存中是怎样存储的,今天用一篇博客给你讲清楚!!!从此不再疑惑!!!

文章目录


1. 整数在内存中的存储

整数的2进制表示方法有三种,即 原码、反码和补码。
有符号的整数,三种表示方法均有符号位和数值位两部分,符号位都是用0表⽰“正”,用1表示“负”,最⾼位的⼀位是被当做符号位,剩余的都是数值位。

正整数的原、反、补码都相同。
负整数的三种表示方法各不相同。

原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。
反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。

对于整型来说:数据存放内存中其实存放的是补码。
为什么呢?

2. 大小端字节序和字节序判断

我们来看一段代码:

int main()
{
	int n = 0x11223344;//十六进制数字
	return 0;
}

我们调试一下,在内存中观察一下:
在这里插入图片描述
我们可以看到,它在内存中是反着存的(以字节为单位存储,但是字节的存储是倒着的)。

这里进行几点说明:

那么为什么是倒着存储的呢?这个就和我们下面要讲到的大小端的问题有关了。

2.1 什么是大小端

  • 那么我们是怎么理解高位节和低位节呢?

当前VS上使用的就是小端存储。

2.2 为什么有大小端

为什么会有大小端模式之分呢?

2.3 练习

2.3.1 练习1

  • 题目:
    请简述⼤端字节序和⼩端字节序的概念,设计⼀个⼩程序来判断当前机器的字节序。(10分)-百度笔试题。

  • 思路:

  • 代码:
#include<assert.h>
#include<stdio.h>
int judge(int* p)
{
	assert(p);
	if (*(char*)p == 1)
	{
		return 1;
	}
	else
	{
		return 0;
	}
}
int main()
{
	int n = 1;
	int ret = judge(&n);
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}
  • 代码优化:
#include<assert.h>
#include<stdio.h>
int judge(int* p)
{
	assert(p);
	return *(char*)p;
}

int main()
{
	int n = 1;
	int ret = judge(&n);
	if (ret == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}

	return 0;
}

2.3.2 练习2

  • 题目:
#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;
}

请问上面代码的输出结果是什么?

在解决这个题之前,我们大家要明确signed char(有符号的) 和 unsigned char(无符号的),这两个东西究竟是什么玩意儿?只有搞明白,我们才能正确做出这道题。

题目解析:

#include <stdio.h>
int main()
{
	//这里说明一下,char 是有符号的char还是无符号的char是取决于编译器的
	//在vs上char==signed char
	char a = -1;
	//10000000 00000000 00000000 00000001 - 原码
	//11111111 11111111 11111111 11111110 - 反码
	//11111111 11111111 11111111 11111111 - 补码
	//但是由于我们定义的是 char 类型的,所以只存进去1个bit位,这个时候就会发生截断
	//只存 11111111 - a
	
	signed char b = -1;
	//11111111 - b

	unsigned char c = -1;
	//11111111 - c

	printf("a=%d,b=%d,c=%d", a, b, c);
	return 0;
}

注意: 上面写的补码都是在内存中存的补码,但是站在不同类型的数据往外面取的时候就不一定是这样了,我们接着往下看:

2.3.3 练习3

  • 题目:
#include <stdio.h>
int main()
{
	char a = -128;
	printf("%u\n", a);//以%u打印是无符号的打印
	return 0;
}

我们来看这道题目的输出结果是什么?

  • 题目解析:
#include <stdio.h>
int main()
{
	char a = -128;
	//100000000 - -128
	//111111111 11111111 11111111 10000000 - 整型提升
	//而%u又认为内存中存的是一个无符号数,
	//正数的原码反码补码都相同,所以就会直接打印。
	printf("%u\n", a);//以%u打印是无符号的打印
	return 0;
}
  • 代码结果:
    在这里插入图片描述

题目:

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

我们再来看这道题目的输出结果是什么?

  • 题目解析:

2.3.4 练习4

  • 题目:
#include <stdio.h>
int main()
{
 char a[1000];
 int i;
 for(i=0; i<1000; i++)
 {
 a[i] = -1-i;
 }
 printf("%d",strlen(a));
 return 0;
}

那么这个代码的结果又是什么呢?

  • 题目解析:

2.3.5 练习5

  • 题目:
#include <stdio.h>
unsigned char i = 0;
int main()
{
 for(i = 0;i<=255;i++)
 {
 printf("hello world\n");
 }
 return 0;
}
  • 题目解析:
  • 题目:
#include <stdio.h>
int main()
{
 unsigned int i;
 for(i = 9; i >= 0; i--)
 {
 printf("%u\n",i);
 }
 return 0;
}
  • 题目解析:

2.3.6 练习6

  • 题目:
#include <stdio.h>
//X86环境 ⼩端字节序
int main()
{
 int a[4] = { 1, 2, 3, 4 };
 int *ptr1 = (int *)(&a + 1);
 int *ptr2 = (int *)((int)a + 1);
 printf("%x,%x", ptr1[-1], *ptr2);
 return 0;
}

同样的,这段代码的运行结果是什么?

  • 题目解析:

3. 浮点数在内存中的存储

3.1 练习

接下来,我们先来看一段代码:

#include <stdio.h>
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;
 }

大家先来自己看一下这个代码运行的结果是多少,然后再往下看答案,感受一下自己的思路和正确的思路偏差在哪.
我们从上往下一行一行地先大概来分析一下这段代码。

#include <stdio.h>
int main()
{
	int n = 9; //定义了一个int类型的常量n, 并赋值为9.
	float* pFloat = (float*)&n;//取出了n的地址,强制类型转换成( float* )类型,紧接着将其赋给float*类型的指针变量pFloat。
	printf("n的值为:%d\n", n);//以%d的形式打印n
	printf("*pFloat的值为:%f\n", *pFloat);//以%f的形式打印*pFloat

	*pFloat = 9.0;//通过指针间接访问n,并将其值改为9.0
	printf("num的值为:%d\n", n);//以%d的形式打印n
	printf("*pFloat的值为:%f\n", *pFloat);//以%f的形式打印*pFloat
	return 0;
}

看到这里你是不是还以为答案是9,9.0,9,9.0.
其实并不是,我们来看一下运行结果:
在这里插入图片描述
看完答案,你是不是有很多疑惑,没关系,我们带着疑问往下走,会得到答案的,我们一点一点地分析到底为什么是这样的一个答案。

我们知道,&n是int类型的,占4个字节,强制类型转换成float类型的之后,也是占4个字节啊,我们在解引用的时候应该是恰好能访问它本来存储的4个字节的空间的呀!
在这里插入图片描述

那么按理说,答案就应该是我们上面所说的答案啊,可是为什么那不是正确答案呢?
那就说明我们的浮点数在内存中的存储并不是像我们想的那样,不是和整数一样的。

3.2 浮点数的存储

根据国际标准IEEE(电⽓和电⼦⼯程协会) 754,任意⼀个⼆进制浮点数V可以表⽰成下⾯的形式:

举例来说:

那么我们按照国际标准表示的5.5就是5.5 = (-1)^0 * 1.011 * 2 ^ 2(这样写不太好看,请看如下图)
在这里插入图片描述
在这里,S=0, M=1.011, E=2,因为所有的浮点数都能按照这个形式表示出来,只不过是不同的浮点数的S, M, E的值是不同的,而这个形式中的其他数据是不变的,那么就意味着,我们在存储浮点数的时候只需要将S, M, E存储好就行了,事实上,浮点数在内存中的存储,九四存储的S, M, E相关的值。

那么浮点数在内存中究竟是怎么存储的呢?

3.2.1 浮点数存的过程

IEEE 754 对有效数字M和指数E,还有⼀些特别的规定。

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

这里大家可能会有一个疑惑,如果这个负数加上中间数之后,还是一个负数该怎么办呢?

接下来,我们就还以5.5为例来看一下浮点数在内存中的存储究竟是怎样的。

int mian()
{
	float f = 5.5;
	//二进制表示:(-1)^0 * 1.011 * 2^2
	//其中,S = 0, M = 1.011, E = 2
	//S(1位)在内存中的存储:0
	//E(8位)在内存中的存储:2+127(float类型) = 129, 10000001
	//M(23位)在内存中的存储:01100000000000000000000(位数不够时,补0)
	//所以浮点数5.5在内存中的存储是01000000101100000000000000000000
	return 0;
}

那这个时候是不是感觉这个二进制太长了,看着不得劲,那么我们可以将其写成16进制的数字,会比较好看些。

//我们知道,一位16进制的数字对应的是4个2进制的数字。
//0100 0000 1011 0000 0000 0000 0000 0000
//4    0    B    0    0    0    0    0
//40 B0 00 00

我们来调试一下,看看内存中的存储方式是不是和我们说的一样。
在这里插入图片描述
我们在这里看到果真和我们写的一样(这里提示一下,大家看到内存中是反过来存储的,这是小端存储,不太了解的同学可以往这篇博客的上面翻翻,翻到最上面,看一下目录,就可以直接找到大小端的讲解)。

大家在举例子的时候可不敢乱举哈,你看完之后,特别兴奋,感觉自己又掌握了一个新知识,你就开始随便举例子,比如,你举了一个3.14这个浮点数,你会发现你用二进制表示的时候,无法精确的表示3.14,无论你怎么用小数点后的01来凑0.14,你可能都无法精确的凑出来。也就是说,如果小数点后的位太多,就可能导致浮点数在内存中无法精确的保存。所以,浮点数在内存中可能会出现这种情况,无法在内存中精确保存。

3.2.2 浮点数取的过程

前面介绍了浮点数存的过程,那么浮点数在取的时候又是怎样的呢?
常规情况下,就是按照存的相反过程往回返就可以了。
但是也有不常规的情况。在浮点数取的过程分为3种情况。

3.3 题目解析

下⾯,让我们回到上面的练习,来看解析吧,同志们!

int main()
{
	int n = 9;
	//00000000 00000000 00000000 00001001----9在内存中的二进制补码

	float* pFloat = (float*)&n;
	printf("n的值为:%d\n", n);
	//n是int类型的,又以%d的形式输出,这里输出9是没有任何问题的

	printf("*pFloat的值为:%f\n", *pFloat);
	//这里是以%f的形式打印的,那么站在pFloat的角度来看,
	//它会认为自己指向的是一个float类型的数据,那么9在内存中的存储就是下面这样的了
	//   0 00000000 00000000000000000001001
	//   S    E            M
	//那么,这个时候我们要取出来这个数据,就是我们说的E全为0的情况,
	//那么,它就是一个非常非常小的数字,是一个无限接近于0的数字

	*pFloat = 9.0;
	//将9.0这个浮点数通过指针变量pFloat间接访问n,pFloatde 的类型是float*类型的
	//此时,pFloat是以浮点数的视角存储9.0的
	//我们按照标准规定写出9.0
	//(-1)^0 * 1.001 * 2^3
	//此时,S=0, M=1.001, E=3
	//0 10000010 00100000000000000000000
	printf("num的值为:%d\n", n);
	//此时,n已经被pFloat按照浮点数的视角存进去了,此时打印要以整数的的视角往回拿
	//那么此时整数的视角的二进制序列就是下面这样的:
	//01000001 00010000 00000000 00000000
	//那么此时以%d的形式打印,%d表示的是有符号的,那么最高位就是符号位,最高位是0,是正数
	//那么补码就是其原码,直接 打印出来就好了。

	printf("*pFloat的值为:%f\n", *pFloat);
	//前面我们说过存的时候是按照float的视角存进去的,那取的时候也是按照float取的话,
	//那么,和存进去的时候是没差的,所以打印的就是9.0。
	return 0;
}

举报

相关推荐

0 条评论