当前位置: 首页 > news >正文

数据的存储

目录

数据类型介绍

类型的基本归类

整形在内存中的存储

原码反码补码

机器的大小端

练习

浮点数在内存中的存储

如何存

如何取


 

数据类型介绍

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

char        //字符数据类型
short       //短整型
int         //整形
long        //长整型
long long   //更长的整形
float       //单精度浮点数
double      //双精度浮点数

注意:C语言没有字符串类型!!!

类型的意义:

1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。

2. 如何看待内存空间的视角。

类型的基本归类

整形家族:

char
    unsigned char
    signed char
short
    unsigned short[int]
    signed short[int]
int
    unsigned int
    signed int
long
    unsigned long[int]
    signed long[int]

浮点数家族:

float

double

构造类型:

数组类型

结构体类型

枚举类型

联合类型

指针类型:

int pi;
char pc;
float
pf;
void
pv;

空类型:

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

整形在内存中的存储

我们都知道,创建变量是要在内存中开辟空间的,给变量赋值本质是将值存放在了申请的内存空间上,而计算机只认识0/1,那么一个数在内存中是如何存储的呢???

原码反码补码

计算机中的整数有三种表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示正,用1表示负,而对于数值位,正数和负数的表示方法不同:

负数

原码:直接将原数按照正负数的形式翻译成二进制就可以

反码:原码的符号位不变,其他位按位取反

补码:反码+1

正数:原码反码补码是一样的

重点:在内存中,存储的是整数的补码形式

原因:在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

我们分别定义了一个正整数和一个负整数,并手动推到了原码反码补码以及十六进制表示,然后调试观察a和b在内存中的存储,发现整体和我们推导的一致,内存中的确存储的是整数的补码形式,但是字节的顺序好像不太一致~

机器的大小端

大小端的概念

大小端,或称为字节序,指的是在计算机系统中,对于一个多字节的数据类型(如int, short, long等),其各个字节在内存中是如何存放的。(因为内存单元划分的最小单元就是字节,所以不会存在小于1个字节的比特位的存放顺序,只会存在大于1个字节的各个字节如何存放的问题)

大端模式:数据的高位字节存放在内存的低地址处,低位字节存放在内存的高地址处。

小端模式:数据的低位字节存放在内存的低地址处,高位字节存放在内存的高地址

这里的“高位字节”和“低位字节”可以类比我们书写数字:在数字 0x12345678 中,12 是最高位(万位),78 是最低位(个位)。

大小端的适用场景

• 大端模式的优点在于符号位存在于第一个字节,容易快速判断数据的正负和大小。

• 小端模式的优点在于,当进行数据类型转换(例如从 short 转换为 int)时,不需要调整字节内容,因为低位字节就在低地址。

注:大多数的机器都采用的小端模式

判断机器的大小端

#include <stdio.h>
int check_sys()
{int a = 1;//地址: 低  -> 高    //大端: 00 00 00 01//小端: 01 00 00 00return *(char*)&a;
}int main()
{if (check_sys() == 1)printf("小端\n");elseprintf("大端\n");return 0;
}
练习

总结:我们来总结一下常见类型的数值的取值范围:

signed char

//以下展示的都是补码
0000 0000  0
0000 0001  1
0000 0010  2
0000 0011  3
...
0111 1111  127
1000 0000  -128(直接被解析成-128)
1000 0001  -127
...
1111 1110  -2
1111 1111  -1

unsigned char

0000 0000  0
0000 0001  1
0000 0010  2
0000 0011  3
...
0111 1111  127
1000 0000  128
1000 0001  129
...
1111 1110  254
1111 1111  255

同理可得:

signed int:  [-2^31,2^31-1]

unsigned int: [0,2^32-1]

其他类型依此类推

eg1:

#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, 255return 0;
}
a/b:
原码: 10000000 00000000 00000000 00000001
反码: 11111111 11111111 11111111 11111110
补码: 11111111 11111111 11111111 11111111
存放到char类型变量中发生截断:
11111111
打印的时候整形提升, 由于是%d, 所以补符号位
补码: 11111111 11111111 11111111 11111111
反码: 11111111 11111111 11111111 11111110
原码: 10000000 00000000 00000000 00000001
所以打印结果是-1c:
原码: 10000000 00000000 00000000 00000001
反码: 11111111 11111111 11111111 11111110
补码: 11111111 11111111 11111111 11111111
存放到char类型变量中发生截断:
11111111
打印的时候整形提升, 由于是%d, 所以直接补0, 无符号数也不存在原码补, 直接打印即可
00000000 00000000 00000000 11111111
所以打印结果是255

eg2:

#include <stdio.h>
int main()
{char a = -128;printf("%u\n", a);return 0;
}
a:
原码: 10000000 00000000 00000000 10000000
反码: 11111111 11111111 11111111 01111111
补码: 11111111 11111111 11111111 10000000
存放到char中, 发生截断
10000000
又因为格式化是%u, 所以先要进行整形提升, a是char类型, 所以补符号位1
11111111 11111111 11111111 10000000
因为是以%u打印, 所以printf认为是一个无符号数, 那么直接计算即可
所以打印 4294967168

eg3:

#include <stdio.h>
int main()
{char a = 128;printf("%u\n", a);return 0;
}
a 原/反/补: 00000000 00000000 00000000 10000000
存放到char中, 发生截断
10000000
又因为格式化是%u, 所以先要进行整形提升, a是char类型, 所以补符号位1
11111111 11111111 11111111 10000000
因为是以%u打印, 所以printf认为是一个无符号数, 那么直接计算即可
所以打印 4294967168

eg4:

#include <stdio.h>
int main()
{int i = -20;unsigned int j = 10;printf("%d\n", i + j);return 0;
}
i:
原码: 10000000 00000000 00000000 00010100
反码: 11111111 11111111 11111111 11101011 
补码: 11111111 11111111 11111111 11101100j 原反补:
00000000 00000000 00000000 0000101011111111 11111111 11111111 11101100  -i补码
00000000 00000000 00000000 00001010  -j补码
11111111 11111111 11111111 11110110  相加-补码
11111111 11111111 11111111 11110101  反码
10000000 00000000 00000000 00001010  原码
-10

eg5: 

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

结果是打印死循环,因为unsigned int不可能是负数,范围是[0,2^32-1]

eg6:

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

strlen遇到'\0'才结束并且统计字符个数不包含'\0','\0'的字面值是0,因此这道题的本质就是求循环了多少次a[i]是0,根据上面的圆形图,我们知道,从-1到-128是128个数,从127到1,是127个数,加起来是255

eg7:

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

死循环打印 hello wolrd,因为unsigned的最大值就是255,永远不会跳出循环

浮点数在内存中的存储

一个例子:

#include <stdio.h>
int main()
{int n = 9;float* pFloat = (float*)&n;printf("n的值为:%d\n", n); //9printf("*pFloat的值为:%f\n", *pFloat); //0.000000*pFloat = 9.0;printf("num的值为:%d\n", n); //1091567616printf("*pFloat的值为:%f\n", *pFloat); //9.0return 0;
}

我们虽然目前不完全清楚上述代码为啥是这样的打印结果,但我们可以得到初步的结论:整形在内存中存储方式和浮点数在内存中存储方式必然不同!否则按照整数存进去,按照浮点数取出来应该结果相同呀;按照浮点数存进去,按照整数取出来,结果也应该相同!

如何存

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

例如十进制的5.5,二进制是 101.1,写成科学计数法就是 -1^0 * 1.011 * 2^2,S是0,M是1.011,E是2

存储时有效数字M的规定:

前面说过, 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是可以出现负数的,所以IEEE 754规定,存入内存时E的真实值必须再加上一个中间数,对于8位的E,这个中间数是127;对于11位的E,这个中间  数是1023。

比如,2^10的E是10,所以保存成32位浮点数时,必须保存成10+127=137,即  10001001。

下面我们举例说明5.5是如何在内存中存储的

如何取

E不全为0或不全为1:

E-127,有效数字M前加 “1.” 即可

E全为0

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

E全为1

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

下面我们来解释一下最开始代码输出的结果

#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;
}
①: 以整数形式存储, 以整数形式获取, 得到的就是9②:
9在内存中的存储:
00000000 00000000 00000000 000001001
以浮点数方式获取, 会如何看待存储方式
0(S) 00000000(E) 000000000000000000001001(M)
E是全0, 因此是很小的一个数, -1^0 * 0.000000000000000000001001 * 2^(-127)
%f打印, 精度到第六位, 因此打印0.000000③
9.0的科学计数法: (-1)^0 * 1.001 * 2^3
在内存中的存储:
0 10000010 00100000000000000000000
以整数的方式获取, 会当成 01000001000100000000000000000000 求原码, 最高位是0, 原码还是该数
打印出来是 1,091,567,616④: 以浮点数形式存储,以浮点数形式获取, 得到的就是9.0

http://www.dtcms.com/a/523989.html

相关文章:

  • GJOI 10.20/10.22 题解
  • Linux:权限(完结)|权限管理|修改权限chmod chown charp|文件类型|拓展
  • (一)仓库创建与配置 - .git 目录的结构与作用
  • Office 2010 64位 补丁 officesp2010-kb2687455 安装步骤详解(附安装包)
  • 建免费网站建设银行网站能不能注销卡
  • springboot中的怎么用JUnit进行测试的?
  • LeetCode:695. 岛屿的最大面积
  • 传奇手游可以使用云手机挂机搬砖吗
  • 2025 OSCAR丨与创新者同频!Apache RocketMQ 邀您共赴开源之约
  • Dify配置本地部署的音频识别模型
  • C# .NET Core中Chart图表绘制与PDF导出
  • 相机拍照的图片怎么做网站呀国内互联网公司排名
  • 微信怎么建设自己网站在单机安装wordpress
  • 实验-Vlan基础
  • Windows CMD 常用命令:7 大核心模块速查指南(附实战场景)
  • OCR国内外证件识别接口调用指南-身份证文字识别
  • 使用acme.sh创建自己的第一个https证书
  • Galera Cluster部署
  • 【Flink实战】升级HDFS,对Flink SQL(On Yarn模式)的功能兼容性验证
  • LangChain 表达式语言:SQL 数据库查询链
  • 通辽网站网站建设网站卖东西怎么做
  • 免费个人网站建设大全有什么建设网站的书籍
  • 电脑控制DFPlayer Mini MP3播放音乐
  • Day10:Python实现Excel自动汇总
  • 网站建设 美食站点网站设计确认函
  • 新买的笔记本电脑为什么风扇声音一直很大?怎样解决?
  • 鸿蒙 HarmonyOS 6|ArkUI(03):状态管理
  • DeepSeek 最新开源OCR模型,实测,不如百度Paddle
  • 做视频网站多大空间够网络推广是指什么
  • 网站运营维护中需要用到什么服务器网站设计哪家最好