数据在内存的存储

devtools/2025/1/13 14:09:37/

数据类型介绍

前面我们已经学习了基本的内置类型:

char        //字符数据类型   1字节  打印%c           
short       //短整型       2字节    打印%hd
int         //整形      4字节   打印%d
long     =long int   //长整型        4/8字节        打印%ld
long long  =long long int //更长的整形    8字节    打印%lld
float       //单精度浮点数                4字节        打印%f
double      //双精度浮点数                8字节     打印%lf
sizeof(long)>=sizeof(int)

整形打印无符号数将格式中的d换成u就行

 类型的基本归类

整形家族:
char
unsigned char
signed char
f63cdb63e76a43d39906b89d5d62a446.png
short
unsigned short [int]
signed short [int]
int
unsigned int
signed int
long
unsigned long [int]
signed long [int]
long long
浮点数家族:
float
double
long double
构造类型:
> 数组类型
45487158c3b44ff7a7c79d95776e608a.png
> 结构体类型 struct
> 枚举类型 enum
> 联合类型 union
指针类型
int *pi;
char *pc;
float* pf;
void* pv;
结构体指针等等

ad35a26ffc9345c2b50567bdfb371e4f.png

整形在内存中的存储

我们之前讲过一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的。那接下来我们谈谈数据在所开辟内存中到底是如何存储的?
比如:
int a = 20;
int b = -10;
我们知道为 a 分配四个字节的空间。
那如何存储?
下来了解下面的概念:
原码、反码、补码
计算机中的整数有三种2进制表示方法,即原码、反码和补码。
三种表示方法均有 符号位数值位两部分,符号位都是用0表示“正”,用1表示“负”,而数值位
正数的原、反、补码都相同。 负整数的三种表示方法各不相同。
原码
直接将数值按照正负数的形式翻译成二进制就可以得到原码。
反码
将原码的符号位不变,其他位依次按位取反就可以得到反码。

 

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

下边我们看一个数据存储的例子就好理解了

67858f98a9b8438eb9869d248b0bcf57.png

b55a4fd3101e466b8f30c0372dadd4f4.png

可以看到,数据在内存是补码存储的,并且是倒着存储的,本质上内存中存放的是2进制,为了方便显示,我们看到的都是16进制的数字(不要害怕不同进制的数字,本质上进制只是表示数字的一种形式)为什么是这样存储的呢接下来我带大家看一下

讲大小端之前我先说明一下原码反码补码的关系(整数原码,反码,补码相同,负数如下)
ee0bbfeeea314d2b85dafe630adc5b50.png
原码-》补码,先除了符号位按位取反,再+1
补码-》原码,先-1,再除了符号位按位取反
大小端介绍
什么大端小端:
大端(存储)模式,是指数据的低位字节保存在内存的高地址中,而数据的高位字节,保存在内存的低地址中;
小端(存储)模式,是指数据的低位字节保存在内存的低地址中,而数据的高位字节,,保存在内存的高地址中。
我们测试一下这个数据
081ce3fcc05445658f54b73ef3083de8.png
fda0699bcd4049bab4645610a8d3aa3e.png
我们知道数据在内存是从低地址向高地址存储的
一个数据的低位字节如果存放在内存的低地址处,代表是小端存储,反之是大端存储
有一道笔试题
百度2015年系统工程师笔试题:
请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。(10分)
//小端返回1
//大端返回0
int check_sys()
{int a = 1;return *(char*)&a;
}int main()
{int ret = check_sys();if(ret == 1)cout<<"小端"<<endl;elsecout << "大端" << endl;return 0;
}

思路是什么呢

c7096f7b54524d3e8c8af253831a37c6.png

我们用char* 强转a的地址,解引用一个字节,如果拿到的是1,代表是小端存储,图上清晰明了很简单

做题之前我们介绍一个东西( 大致意思就是数据类型是有存储范围的,我们介绍char的存储范围,其他类型可以照这个类比,是很简单的)(char类型一个字节,有符号char 第一位是符号位,不参与计算,1代表负数,0代表正数,数据范围是-128~127;无符号char第一位是数值位,也参与计算,全部为正数)(所以相同类型无符号数值上限要比有符号数值高一倍)
22cfc74c112e4d15a5b55a79899b0a42.png
整形家族有这么多类型,我们怎么判断数据在内存的存储呢(注意,数据在内存中一定是以补码存储的,我们计算数据也是计算补码)
在vs上我们认为没有unsigned就是有符号类型,有unsigned就是无符号类型,有符号类型第一位存储符号位(正数是0,负数是1),无符号类型每一位都是数值位,一个字节等于8比特位,具体的计算过程上图很明显了(注意:有符号类型第一位符号位不参与计算,只算数值位,所以相对无符号类型的最大值会小一半)
adec8cbfc6a04ec1a2a939654bd699b6.png
有符号数最高位是1整型提升高位补充符号位1,最高位是0整型提升高位补充符号位0
无符号数整型提升高位补0
1.
//输出什么?
#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);//-1 -1 255
return 0;
}
先算出补码,后截断,再根据类型整型提升,最后打印(以有符号整形打印需要看最高位,是1说明是负数,需要转化为原码,是0说明是正数,正数原反补相同,直接打印的就是原码,以无符号打印直接补码就是原码(不需要关心符号位,因为无符号数没有符号位,所有位都是数值位)

#include <stdio.h>
int main()
{char a = -1;//10000000000000000000000000000001//11111111111111111111111111111110//11111111111111111111111111111111-截断//11111111 -a//11111111111111111111111111111111//11111111111111111111111111111110//10000000000000000000000000000001--> -1signed char b = -1;//11111111111111111111111111111111//11111111 -bunsigned char c = -1;//11111111 -c//00000000000000000000000011111111//printf("a=%d,b=%d,c=%d", a, b, c);//%d - 十进制的形式打印有符号整型整数//整型提升return 0;
}
2.
#include <stdio.h>
int main()
{
char a = -128;
printf("%u\n",a);//%u是以十进制形式打印无符号整数
return 0;
}
//#include <stdio.h>
//int main()
//{
//	char a = -128;
//	//-128
//	//10000000000000000000000010000000//原码
//	//11111111111111111111111101111111//反码
//	//11111111111111111111111110000000//补码
//	//-128的补码
//	//10000000//截断
//	//11111111111111111111111110000000//有符号最高位补1,进行整型提升
//	//
//	printf("%u\n", a);
//	return 0;
//}

976c56b96cfe47ccac29517f747fa8d1.png打印42亿多

3.
#include <stdio.h>
int main()
{
char a = 128;
printf("%u\n",a);
return 0;
}//这道题跟上边的题结果一样因为截断处都是1000 0000,都是有符号数,以无符号形式打印,所以结果都一样
4.
int i= -20;
unsigned  int  j = 10;
printf("%d\n", i+j);
//按照补码的形式进行运算,最后格式化成为有符号整数
//int main()
//{
//	int i = -20;
//	//10000000000000000000000000010100
//	//11111111111111111111111111101011
//	//11111111111111111111111111101100//-20的补码
//	//
//	unsigned int j = 10;
//	//00000000000000000000000000001010//10的补码
//	//11111111111111111111111111101100
//	//11111111111111111111111111110110//相加和是补码,要以有符号形式打印,符号位是1是负数,补码要转化为原码进行打印
//	//11111111111111111111111111110101
//	//10000000000000000000000000001010 -10//转化为原码//	printf("%d\n", i + j);//-10
//	return 0;
//}
//
5.
unsigned int i;
for(i = 9; i >= 0; i--)
{
printf("%u\n",i);
}
0da2a6abef4a4ddaa3b5f85f86c7ebcd.png
因为i是无符号整形,数据范围是0到42亿多,所以0--之后会是42亿多(-1的补码是32位全1,所以是42亿多)
6.
int main()
{   
char a[1000];
int i;   
for(i=0; i<1000; i++)
{       
a[i] = -1-i;
}
printf("%d",strlen(a));  
return 0;
}
e54b000991c64cb68712de27b6ea6178.png
char是有符号char,最多表示-128到127,所以数组里边只会存连续相同的,-1到-128,再从127到0,这是一轮循环,strlen函数是判断长度,到\0结束,因为\0的ascll码值是0,所以strlen到第一轮0就会结束,0之前是255个字符,所以打印255
7.
#include <stdio.h>
unsigned char i = 0;
int main()
{for(i = 0;i<=255;i++){printf("hello world\n");}return 0;
}

这个就很简单了,无符号char范围是0~255,只要进入循环i永远小于255,所以死循环

浮点型在内存中的存储

常见的浮点数:
3.14159
1E10
浮点数家族包括: float、double、long double 类型。
浮点数表示的范围:float.h中定义
8c94053589c44e2d9f67158f5a443245.png
浮点数存储的例子:
#include<iostream>
using namespace std;
#include <stdio.h>int main()
{int n = 9;////0 00000000 00000000000000000001001//S  E        M//0  -126     0.00000000000000000001001//(-1)^0 * 0.00000000000000000001001 * 2^-126////E在内存中是全0//float* pFloat = (float*)&n;printf("n的值为:%d\n", n);//9printf("*pFloat的值为:%f\n", *pFloat);//0.000000*pFloat = 9.0;//1001.0//1.001 * 2^3//(-1)^0 * 1.001 * 2^3//S=0     E=3  M=1.001//0 10000010 00100000000000000000000//printf("num的值为:%d\n", n);//1091567616printf("*pFloat的值为:%f\n", *pFloat);//9.0return 0;
}

先把这个代码放在这,现在先描述一下浮点数如何在内存存储,再返回来看这道题

72ed468c19104450bfcae7bc13db1278.png

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

d66c860bd00f46819a9dd7d3c715af9e.png

第一个是float,第二个是double

下边有点长,我把关键部分加粗了,理解起来轻松些

浮点数存储规则
num 和 *pFloat 在内存中明明是同一个数,为什么浮点数和整数的解读结果会差别这么大?
要理解这个结果,一定要搞懂浮点数在计算机内部的表示方法。
详细解读:根据国际标准IEEE(电气和电子工程协会) 754,任意一个二进制浮点数V可以表示成下面的形式:
(-1)^S * M * 2^E
(-1)^S表示符号位,当S=0,V为正数;当S=1,V为负数。
M表示有效数字,大于等于1,小于2。
2^E表示指数位。
举例来说:
十进制的5.0,写成二进制是 101.0 ,相当于 1.01×2^2
那么,按照上面V的格式,可以得出S=0,M=1.01,E=2。
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位有效数字。 (提高数字的精度,意思就是说M只存小数位,不够补0)
至于指数 E ,情况就比较复杂。
首先, E 为一个无符号整数( unsigned int
这意味着, 如果 E为8位,它的取值范围为0~255;如果E为11位,它的取值范围为0~2047 但是,我们知道,科学计数法中的E是可以出现负数的, 所以IEEE 754 规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间
数是1023。比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即
10001001。然后,指数E从内存中取出还可以再分成三种情况:
E 不全为 0 或不全为 1(绝大多数情况,意思就是说怎么放进去怎么拿出来),(放进去+127/1023,拿出来-127/1023)
这时,浮点数就采用下面的规则表示, 即指数 E的计算值减去127(或1023),得到真实值,再将有效数字M前加上第一位的1。
比如: 0.5(1/2)的二进制形式为0.1,由于规定正数部分必须为1,即将小数点右移1位,则为 1.0*2^(-1),其阶码为-1+127=126,表示为01111110,而尾数1.0去掉整数部分为0,补齐0到23位00000000000000000000000,则其二进制表示形式为:00111111000000000000000000000000
E全为0(说明放进去之前是0-127/1023,表示为2^-127/2^-1023,是一个很小的值,可以忽略不计了)(这种情况了解一下就行)
这时, 浮点数的指数 E等于1-127(或者1-1023 )即为真实值,有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0 ,以近于0的很小的数字。
E 全为 1(说明放进去之前是127/,反正是一个很大的值,也不做重点讨论,了解)
这时,如果有效数字M全为0,表示±无穷大(正负取决于符号位s);
好了,关于浮点数的表示规则,就说到这里。
上边大概就是讲,我们在存储一个浮点数时,要将10进制先转化为2进制,根据国际委员会的标准算出SEM具体值,然后进行按规则存储。
有了上边的基础,下边这道题就很好理解了
#include <stdio.h>
int main()
{float f = 5.5;//101.1是2进制的5.5//1.011 * 2^2,将2进制类比位10进制进行表示很简单//(-1)^0 *1.011 * 2^2//将对应的数值填入表达式中//S = 0//M = 1.011//E = 2//0100 0000 1011 0000 0000000000000000//0x40b00000return 0;
}

对照上边的浮点数存储模型,5.5是单精度浮点数,对照单精度存储结构,S=0,M=1.011,E=2

S第一个位置就填0,E为2放进去要加127是129,二进制是10000001 ,小数点后边有三位是011,M有23比特位,011后边全补0二进制序列就没毛病了,上边我写的也有

换算成16进制是0x40b00000

d2a18eb25d6e45a399b0aea6deed1bbf.png

可以很清晰的看到内存中是小端存储,我们算的是对的,只要按照这个算法99%的浮点数我们都会算。

理解完了这道题,我们还要再看一下刚才那道题

注意,我们以整形存进去并且以整形往出拿,和以浮点型存进去并且以浮点型往出拿的结果都很单纯,以整形存进去但以浮点数往出拿和以浮点数存进去以整形往出拿的结果大大不同。

#include<iostream>
using namespace std;
#include <stdio.h>int main()
{int n = 9;////0 00000000 00000000000000000001001//S  E        M//0  -126     0.00000000000000000001001//(-1)^0 * 0.00000000000000000001001 * 2^-126////E在内存中是全0//float* pFloat = (float*)&n;printf("n的值为:%d\n", n);//9printf("*pFloat的值为:%f\n", *pFloat);//0.000000*pFloat = 9.0;//1001.0//1.001 * 2^3//(-1)^0 * 1.001 * 2^3//S=0     E=3  M=1.001//0 10000010 00100000000000000000000//printf("num的值为:%d\n", n);//1091567616printf("*pFloat的值为:%f\n", *pFloat);//9.0return 0;
}

整数n在内存中正常存储,原码反码补码相同,注意,我们计算的永远是内存中的补码,打印打的是内存中的原码,当把整数int类型以浮点数形式往出拿的时候,根据国际标准IEEE(电气和电子工程协会) 754标准,将内存以SME表示出来,S是0,E是全0,我们刚才说过E是全0,说明我们拿出来是E是-126,M就是0.00000000000000000001001。可以看到,只要根据公式计算,就很简单。(结果是(-1)^0 * 0.00000000000000000001001 * 2^-126  打印结果是0.00000000,一个很小的数,符合我们的预期)

当我们以浮点数的形式存进去,以整数形式拿出来,9.0二进制表示为1001.0,科学计数法是1.001*2^3,根据IEEE协会标准算出来S E M分别是0,3,1.001,再以刚才的规则将其填进内存,最后以有符号形式打印,符号位是0,是正数,以有符号整数打印,正数的原码反码补码相同,将其以原码形式打印出来就是10亿这个数字。很简单吧

以上就是我对数据类型以及数据存储的介绍,感谢支持!!!以后会创作更多的文章

 


http://www.ppmy.cn/devtools/150158.html

相关文章

机器学习的基本原理和算法

机器学习的基本原理是通过从数据中学习模式和规律&#xff0c;以便能够对新数据进行预测或决策。其核心思想是通过训练算法来自动地从数据中学习&#xff0c;并根据学习到的模式和规律进行预测和决策。 机器学习基本原理的深化 数据驱动&#xff1a; 机器学习依赖于大量数据来…

GraphQL:强大的API查询语言

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

[c#] 度分秒和度的转换

一、度分秒转换为度 概念理解 在度分秒表示法中&#xff0c;1度 60分&#xff0c;1分 60秒。例如&#xff0c;一个角度表示为1030′15″&#xff0c;这里的10是度&#xff0c;30是分&#xff0c;15是秒。转换公式及代码示例 转换公式为&#xff1a;度分/60秒/3600。以下是一…

Spring Boot中的依赖注入是如何工作

Spring Boot 中的依赖注入&#xff08;Dependency Injection&#xff0c;简称 DI&#xff09;是通过 Spring 框架的核心机制——控制反转&#xff08;Inversion of Control&#xff0c;IOC&#xff09;容器来实现的。Spring Boot 基于 Spring Framework&#xff0c;在应用中自动…

AI开发 - 算法基础 递归 的概念和入门(三)递归的进阶学习

前面我们通过2篇文章&#xff0c;一起了解了 递归&#xff0c;以及使用递归来解决汉诺塔问题。今天我们在这个基础上&#xff0c;进一步地熟悉和学习递归。 这篇学习笔记将涵盖递归的基本概念、应用、优化技巧、陷阱及与迭代的对比&#xff0c;并通过具体的 Python 代码示例和…

Github出现复杂问题 无法合并 分支冲突太多 如何复原

目录 问题再现 解决思路 当然我所指的是在 main 分支开一个新的分支 删除本地文件夹 重新克隆 开一个新分支 切换分支 下载远程分支 文件覆盖 合并到主分支 ​​​​​​​问题再现 太复杂了 无法更改 编译器现状 全部崩溃了 无法更改 即使创建一个新的分支也无济于…

Pytest-Bdd-Playwright 系列教程(完结篇):本框架的功能参数说明

Pytest-Bdd-Playwright 系列教程&#xff08;完结篇&#xff09;&#xff1a;本框架的功能参数说明 简介1. 浏览器设置2. 环境与设备配置3. 存储状态管理4. 测试用例筛选5. 并行与重试控制6. 报告生成与输出格式7. 其他功能附录&#xff1a;参数说明表 简介 本框架支持多种浏览…

从攻击视角探讨ChatGPT对网络安全的影响

ChatGPT是OpenAI 发布的基于人工智能的对话机器人&#xff0c;上线短短2个月活跃用户就突破了1亿&#xff0c;成为全球关注的焦点。ChatGPT可以自动化地处理对话&#xff0c;可以通过基于自然语言处理技术的模型、情景模型和语言模型来自动生成文章&#xff0c;甚至可以按照用户…