【C语言】数据的存储

目录

Ⅰ、数据类型介绍

        1.类型的基本归类:

Ⅱ、整形在内存中的存储

         1 .原码、反码、补码

        2. 大小端介绍

3 练习:

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

         1 .浮点数存储规则


本章重点
1. 数据类型详细介绍
2. 整形在内存中的存储:原码、反码、补码
3. 大小端字节序介绍及判断
4. 浮点型在内存中的存储解析

Ⅰ、数据类型介绍

前面我们已经学习了基本的内置类型:
                                                空间大小(字节)
char         //字符数据类型                1
short       //短整型                           2
int         //整形                                 4
long         //长整型                           4
long long   //更长的整形                 8
float       //单精度浮点数                 4
double       //双精度浮点数              8
类型的意义:
1. 使用这个类型开辟内存空间的大小(大小决定了使用范围)。
2. 如何看待内存空间的视角。

        1.类型的基本归类:

整形家族:
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
构造类型:
> 数组类型
> 结构体类型 struct
> 枚举类型 enum
> 联合类型 union
指针类型:
int * pi ;
char * pc ;
float* pf ;
void* pv ;
空类型:
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型。

Ⅱ、整形在内存中的存储

一个变量的创建是要在内存中开辟空间的。空间的大小是根据不同的类型而决定的。
接下来我们谈谈数据在所开辟内存中到底是如何存储的。
比如:
int a = 20 ;
int b = - 10 ;
我们知道为 a 分配四个字节的空间。
那如何存储?
下来了解下面的概念:

         1 .原码、反码、补码

计算机中的整数有三种 2 进制表示方法,即原码、反码和补码。
三种表示方法均有 符号位 数值位 两部分,符号位都是用 0 表示 ,用 1 表示 ,而数值位
正数的原、反、补码都相同。
负整数的三种表示方法各不相同。
原码
直接将数值按照正负数的形式翻译成二进制就可以得到原码。
反码
将原码的符号位不变,其他位依次按位取反就可以得到反码。
补码
反码 +1 就得到补码。
对于整形来说:数据存放内存中其实存放的是补码。
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;
同时,加法和减法也可以统一处理( CPU 只有加法器 )此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

我们看看内存中的储存: 

我们可以看到对于 a b 分别存储的是补码。但是我们发现顺序有点 不对劲
这是又为什么?

        2. 大小端介绍

什么大端小端:

 大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;

小端(存储)模式,是指数据的 低位 保存在内存的 低地址 中,而数据的 高位 , ,保存在内存的 高地址 中。
为什么有大端和小端:
为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit 。但是在 C 语言中除了 8 bit char 之外,还有 16 bit short型,32 bit long 型(要看具体的编译器),另外,对于位数大于 8 位的处理器,例如 16 位或者 32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。
总的来说: 一个地址单元对应一个字节,那一个数据(int,double...)  就可能涉及到多个地址单元,这时候从哪边开始存数据在不同的专家的观点上就出现了不同的意见,从而出现了大小端。
笔试题:
请简述大端字节序和小端字节序的概念,设计一个小程序来判断当前机器的字节序。( 10 分)
这个题我们用两个代码来看,一个是普通的强制类型转换,一个是联合体,联合体我们后面再聊,今天聊强转类型转换:
我们都知道,一个int占4个字节,当我们用int去存储数字1的时候,他只会用其中一个字节去存储他并且数字 1位于32位中的最低位,将数字 1 存入int后,我们将变量的地址取出来强制类型转换为 char* 。由于(char*)类型指针每次解引用只会访问一个字节,他就会访问 int 变量地址的第一个字节,如果我们得到 1 则说明 最低位存到了地位——小端存储,如果得到的是0,则说明高位上的 0 存到了低位——大端存储;
//代码1
#include <stdio.h>
int check_sys()
{
 int i = 1;
 return (*(char *)&i);
}
int main()
{
 int ret = check_sys();
 if(ret == 1)
 {
 printf("小端\n");
 }
 else
 {
 printf("大端\n");
 }
 return 0;
}
//代码2
int check_sys()
{
 union
 {
 int i;
 char c;
 }un;
 un.i = 1;
 return un.c;
}

3 练习:

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);
    return 0;
}
2.
#include <stdio.h>
int main()
{
    char a = -128;
    printf("%u\n",a);
    return 0;
}
3.
#include <stdio.h>
int main()
{
    char a = 128;
    printf("%u\n",a);
    return 0;
}
4.
int i= -20;
unsigned  int  j = 10;
printf("%d\n", i+j); 
//按照补码的形式进行运算,最后格式化成为有符号整数
5.
unsigned int i;
for(i = 9; i >= 0; i--)
{
    printf("%u\n",i);
}
6.
int main()
{
    char a[1000];
    int i;
    for(i=0; i<1000; i++)
   {
        a[i] = -1-i;
   }
    printf("%d",strlen(a));
    return 0;
}
7.
#include <stdio.h>
unsigned char i = 0;
int main()
{
    for(i = 0;i<=255;i++)
   {
        printf("hello world\n");
   }
    return 0;
}

详解:

1.

//练习

//输出什么?
#include <stdio.h>
int main()
{
	char a = -1;
	//10000001  a在内存中的原码
	//11111110  a的补码
	//11111111  a的补码--机器中存储的
	//11111111 11111111 11111111 11111111  整型提升之后的十进制补码
	//10000000 00000000 00000000 00000001  取反加一后得到原码--打印出来即a= -1
	signed char b = -1;
	//在当前编译器环境下,char默认是 有符号char  所以 b和a打印出来是一样的


	unsigned char c = -1;	//虽然c是无符号的char,但是他机器中存的依旧是-1的补码,
							//因为赋值是先将-1存进内存再根据接收类型的需要的需要进行读取
	//10000001  a在内存中的原码
	//11111110  a的补码
	//11111111  a的补码--机器中存储的
	// 
	//打印c的时候是要转为整形,进行整形提升,整形提升是按照符号位提升的,这里C是无符号数
	// 记住::::::这里不是看-1  看的是C  
	//00000000 00000000 00000000 11111111 (无符号) C 进行整形提升后
	//%d  是按照十进制有符号数打印,他会将最高位看作为符号位 所以打印出来为——255
	printf("a=%d,b=%d,c=%d", a, b, c);
	return 0;
}

输出结果

2.

#include <stdio.h>
int main()
{
	char a = -128;
	//10000000  补码

	//11111111 11111111 11111111 10000000  整形提升后的结果
	//%u是按照10进制无符号数打印  这里将a看作无符号数打印不需要取反加一
    //按照补码的形式进行运算,最后格式化成为有符号整数
	printf("%u\n", a);
	return 0;
}

3.

#include <stdio.h>
int main()
{
	char a = 128;
	//此编译器环境下,char默认有符号,128显然超过了有符号char的表示范围,经过编译器处理 a在机器中存的是
	//10000000  a的机器码
	//有符号的a整形提升
	//11111111 11111111 11111111 10000000  整形提升后的结果
	//按照无符号数打印  按照补码的形式进行运算,最后格式化成为有符号整数
	printf("%u\n", a);
	return 0;
}

输出结果跟第二个题一样

4.
int main()
{
	int i = -20;
	//10000000 00000000 00000000 00010100 原码
	//11111111 11111111 11111111 11101100 补码
	unsigned int  j = 10;
	//00000000 00000000 00000000 00001010 原码也是补码
	printf("%d\n", i + j);
	//11111111 11111111 11111111 11101100 
	//00000000 00000000 00000000 00001010
	//11111111 11111111 11111111 11110110 i与j的补码相加
	// 按照有符号十进制数打印,最高位为1 既要取反加一
	//10000000 00000000 00000000 00001010 结果 ==== -10
	//按照补码的形式进行运算,最后格式化成为有符号整数
	return 0;
}

执行结果:

5.

这个题需要了解一些存储规则,一个数的类型是被他的字节限制住的,超过了他能表示的最大的数他就会截断一部分,相当于转圈圈,这里我们以有符号char与无符号char为例:

有符号数char

无符号数char

然后看我们这个题:

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

无符号数int,他最小的数就是0,转圈圈永远不可能会走到负数,所以这个代码会死循环

通过结果我们还可以看见他也是在慢慢减小,当然这里用的是int不容易看出转圈圈效果,换为char就会看的比较清楚。

输出结果:

6.

这一个题也需要上一个题的铺垫

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

他从-1开始减,有符号char最多可以减到-128,然后再减到0,然后再见到-128,以次循环。不过这里,strlen统计的是 ‘\0' 之前的字符个数,我们第一次走到0的时候就会停止统计(’\0'的ascll码值是0);我们开始计算,-1 ~ -128是128个字符,127 ~ 1 是127个字符(0不统计!统计的是0之前的字符)这样一相加 127+128=255

输出结果:

7.

这个题也是像上面的一样,转圈圈,死循环

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

执行结果:这里为了方便看,我多打印了一个 i :

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

常见的浮点数:
3.14159
1E10
浮点数家族包括: float double long double 类型。
浮点数表示的范围: float.h 中定义

         1 .浮点数存储规则

根据国际标准 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
十进制的 -5.0 ,写成二进制是 - 101.0 ,相当于 - 1.01×2^2 。那么, S=1 M=1.01 E=2
IEEE 754 规定:
对于 32 位的浮点数,最高的 1 位是符号位 s ,接着的 8 位是指数 E ,剩下的 23 位为有效数字 M

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

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 位有效数字。
至于指数 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
这时,浮点数就采用下面的规则表示,即指数 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 ,则其二进
制表示形式为 :
0 01111110 00000000000000000000000
E 全为 0
这时,浮点数的指数 E 等于 1-127 (或者 1-1023 )即为真实值,
有效数字 M 不再加上第一位的 1 ,而是还原为 0.xxxxxx 的小数。这样做是为了表示 ±0 ,以及接近于
0 的很小的数字。
E 全为 1
这时,如果有效数字 M 全为 0 ,表示 ± 无穷大(正负取决于符号位 s );
好了,关于浮点数的表示规则,就说到这里。

 这里给一个浮点数存储例子:

int main()
{
	float a = 5.5;
	//101.1
	//(-1)^0 * 1.011 * 2^2
	// S=0 E=2+127=129=10000001  M =01100000000000000000000 
	// 01000000 10110000 0000000000000000 
	//     40		b0		00  00
	//  
	return 0;
}

下面有一个题:

int main()
{
	int n = 9;
	//00000000 00000000 00000000 00001001  整数视角存整数 9
	float* pFloat = (float*)&n;
	//0 00000000 00000000000000000001001  用浮点数时间看待9
	//S=0
	//E=0-127=-127
	//M= 00000000000000000001001
	//*pFloat= (-1)^0 * 00000000000000000001001 * 2^(-127)
	//这个数是远小于0的 而flost只能打印小数点后六位,所以打印结果为  0.000000
	printf("n的值为:%d\n", n);//9
	printf("*pFloat的值为:%f\n", *pFloat);//
	*pFloat = 9.0;
	//9.0 
	//(-1)^0   *   1.001  *  2^3  S=0 E=3+127=130= 10000010  M= 00100000000000000000000
	//01000001 00010000 00000000 00000000  浮点数视角存储 9.0 ——机器码  
	//  41		  10	   00		00
	printf("num的值为:%d\n", n);//01000001 00010000 00000000 00000000按照10进制打印为1091567616
	printf("*pFloat的值为:%f\n", *pFloat);//9.0
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/739993.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

测试卡无法仪表注册问题分析

1、问题描述 00101测试卡无法注册LTE网络&#xff0c;modemlog中发现终端未发起Attach请求&#xff0c;对比正常注册非正常注册的版本&#xff0c;发现正常的多出了ims apn。可以通过ATCGDCONT?来查询modem APN参数。 2、问题分析 目前Modem是一套&#xff0c;没有相关修改。因…

SpringBoot使用滑动窗口限流防止用户重复提交(自定义注解实现)

在你的项目中&#xff0c;有没有遇到用户重复提交的场景&#xff0c;即当用户因为网络延迟等情况把已经提交过一次的东西再次进行了提价&#xff0c;本篇文章将向各位介绍使用滑动窗口限流的方式来防止用户重复提交&#xff0c;并通过我们的自定义注解来进行封装功能。 首先&a…

vue3 element-plus 实现 table表格合并单元格 和 多级表头

多级表头 数据结构比较复杂的时候&#xff0c;可使用多级表头来展现数据的层次关系。 只需要将el-table-column 放置于el-table-column 中&#xff0c;你可以实现组头。 一般可以直接用官网提供的写法&#xff0c;但是有可能数据会比较多的时候&#xff0c;就需要我们稍微改造…

江门电子行业实施MES系统前后对比

在江门电子行业实施MES系统之前和之后的对比可以涉及以下几个方面&#xff1a; 生产效率提升&#xff1a;实施MES系统后&#xff0c;江门电子行业可以实现生产过程的实时监控和优化&#xff0c;减少生产中的浪费和停机时间&#xff0c;提高生产效率。 质量控制改善&#xff1a;…

【稀疏三维重建】Flash3D:单张图像重建场景的GaussianSplitting

项目主页&#xff1a;https://www.robots.ox.ac.uk/~vgg/research/flash3d/ 来源&#xff1a;牛津、澳大利亚国立 文章目录 摘要1.引言2.相关工作3.方法3.1 背景&#xff1a;从单个图像中重建场景3.2 单目前向的多个高斯 4.实验4.14.2 跨域新视角合成4.3 域内新视图合成 摘要 F…

ONLYOFFICE 桌面编辑器8.1最新版本强势来袭!

文章目录 软件介绍一、安装与界面安装过程用户界面 二、性能与稳定性启动速度与响应时间稳定性 三、兼容性与集成文件格式兼容性第三方集成 四、可支持多人协作五、功能齐全的PDF编辑器六、PDF表单七、文档编辑器中的新增功能八、总结九、自己的建议 软件介绍 在现代办公环境中…

Cell2Sentence:为LLM传输生物语言

像GPT这样的LLM在自然语言任务上表现出了令人印象深刻的性能。这里介绍一种新的方法&#xff0c;通过将基因表达数据表示为文本&#xff0c;让这些预训练的模型直接适应生物背景&#xff0c;特别是单细胞转录组学。具体来说&#xff0c;Cell2Sentence将每个细胞的基因表达谱转换…

解决Windows打开Excel时正在访问打印机问题、复制Word文档卡死的问题

Excel打开打印机问题 取消让windows管理默认打印机 把所有打印机删除&#xff08;粗暴&#xff09; 把windows虚拟打印机设置了默认打印机 控制面板》程序》启用或关闭windows功能》安装“MicrosoftXPS文档写入程序” 把默认打印机改成MicrosoftXPSDocumentWriter 复制W…

如何统计每天新增好友删除好友跟收款

自动统计功能可以了解每天员工添加了多少人&#xff0c;删除了哪些好友&#xff0c;并查看已被删除好友的聊天记录&#xff0c;避免有的员工私下走私单或者转移客户。统计转账是为了避免员工收多报少&#xff0c;或者瞒报。 ​​​

0620所学——环境变量、CMake等

https://www.cnblogs.com/bravesunforever/p/10939078.html CMake&#xff1a; https://zhuanlan.zhihu.com/p/659412062 0621: 学会了在Github里创建组织&#xff0c;把本地仓库“同步”&#xff0c;就可以上传到Github&#xff0c;然后学会了把自己的Repos转移到组织里。G…

Unity的ScrollView滚动视图复用

发现问题 在游戏开发中有一个常见的需求&#xff0c;就是需要在屏幕显示多个&#xff08;多达上百&#xff09;显示item&#xff0c;然后用户用手指滚动视图可以选择需要查看的item。 现在的情况是在100个data的时候&#xff0c;Unity引擎是直接创建出对应的100个显示item。 …

Nest系列 - 4. 连接Mysql数据库以及typeOrm介绍

前面我们使用nest g res xxx 自动生成CRUD的代码&#xff0c;不仅简单&#xff0c;而且只能在本地玩。今天我们就来看nest 如何连接数据库&#xff0c;数据库有很多种&#xff0c;我们今天来看连接最常用mysql 数据库&#xff0c;并且使用typeOrm 进行数据库操作 mysql 安装 …

原装GUVCL-T21GH 韩国Genicom紫外线传感器光电二极管原厂代理商

深圳市宏南科技有限公司是韩国GenUV公司的原厂代理商&#xff0c;所售紫外线传感器均来自于原始生产厂商直接供货&#xff0c;非第三方转售。 韩国GENICOM 紫外线传感器 GUVCL-T21GH 特征&#xff1a; 单供电电压工作 电压输出 高灵敏度和良好的日盲性 尺寸小巧紧凑 韩国GENIC…

如何确保每颗螺丝都是合格品质

螺丝&#xff0c;一种用来连接和固定物体的金属件&#xff0c;通常是长有螺纹的金属棒。螺丝有不同种类和尺寸&#xff0c;常见的用途包括组装家具、机械设备和其他结构。连接和固定物体&#xff0c;通过螺丝的螺纹结构&#xff0c;将两个或多个物体牢固地连接在一起。提供调节…

研究上百个小时,高手总结了这份 DALL-E 3 人物连续性公式(上)

上篇 Dall-E 3 讲了常见的 20 个公式&#xff0c;今天单独来讲一下人物连续性公式&#xff0c;这个公式来自 AshutoshShrivastava。 上篇回顾&#xff1a; 效果超好&#xff01;全新 DALL-E 3 必须掌握的 20 种公式使用方法上周末&#xff0c;DALL-E 3 正式加入 ChatGpt&…

playwright vscode 插件源码解析

Playwright vscode插件主要功能 Playwright是微软开发的一款主要用于UI自动化测试的工具&#xff0c;在vscode中上安装playwright vscode插件&#xff0c;可以运行&#xff0c;录制UI自动化测试。 playwright vscode插件主要包括两块功能&#xff0c;功能一是在Test Explorer中…

excel字符串列的文本分列合并

excel表有两列&#xff0c;第一列是“姓名”&#xff0c;第二列是“诊断”&#xff0c;有高血压、糖尿病等。我想出一个统计表&#xff0c;将每个人的诊断分为1-N列&#xff0c;比如张三&#xff0c;第一诊断高血压&#xff0c;第二诊断糖尿病&#xff0c;分列显示。我们可以用…

七人团购新体验:解锁数字时代购物新篇章

在数字化浪潮的推动下&#xff0c;购物体验正迈向新的里程碑。其中&#xff0c;七人团购模式以其独特的魅力和创新性&#xff0c;为消费者带来了前所未有的实惠与便利。现在&#xff0c;让我们一同探索这一新兴购物模式的运作机制与潜在价值&#xff0c;特别是针对一款标价599元…

Excel如果将一个表格拆分为多个表格,文末另赠彩蛋!

前期分享如何用数据透视表将一个表格拆分成多个工作薄Excel一个表格拆分多个表格&#xff0c;你学会了吗&#xff1f; 今天刘小生分享另外一种&#xff0c;如果拆分成多个工作表格文件&#xff01; 如何将一个表格根据部门进行拆分成多个表格&#xff0c;再点对点发送给各部门…

MacBook Pro 忘记root用户密码,重置密码步骤

一、以普通用户名登录系统&#xff0c;并打开terminal终端&#xff0c; 输入&#xff1a;sudo bash sudo bash Password:*****&#xff08;输入当前用户的密码&#xff09; 成功后进入bash-3.2#的命令模式 二、在bash-3.2#命令模式下 输入&#xff1a;sudo passwd root sud…