C语言 数据的存储

news2024/11/13 10:28:22

C语言 数据的存储

  • 一、数据与进制之间的关系
    • 1. 十进制与二进制之间的转换
    • 2. 二进制与十六进制之间的转换
  • 二、整型数据存储
    • 1. 原、反、补码
    • 2. 整型数据在内存中的存储
    • 3. 为什么整型数据存在内存中存储的是补码
    • 4. 有符号和无符号的数据类型
      • 有符号和无符号的存储范围
      • 猜想
    • 5. 关于 char 类型
  • 三、大小端存储
    • 1. 两种存储方式的区别
    • 2. 设计一个程序来判断当前编译器的字节序存储
  • 四、整型提升
  • 五、例题
    • 程序清单1
    • 程序清单2
    • 程序清单3
    • 程序清单4
    • 程序清单5
    • 程序清单6
    • 程序清单7

一、数据与进制之间的关系

我们都知道,计算机存储的数据单位是二进制,要么是 0,要么是 1. 实际上,计算机就是用这种二进制序列来表示某个数值。

但我们也要理解与电子信息数据相关的其他表示方法:十进制、十六进制、八进制。因为在 C语言 中,常常需要用到将这些进制进行一定的转换。

十进制:		(0 - 9)
二进制:		(0 1)
八进制:		(0 - 7)
十六进制:	(0 - 9 a b c d e f)

1. 十进制与二进制之间的转换

下图是数据为 11 的十进制与二进制之间的转换。此外,十进制与十六进制、十进制与八进制相互转换的过程也是同理。

1-1

2. 二进制与十六进制之间的转换

1 个字节 8 位 二进制,恰好可以用两个十六进制数据表示。

1-2

二、整型数据存储

1. 原、反、补码

计算机中的整数有三种表示方法,即原码、反码和补码。
三种表示方法均有符号位和数值位两部分。符号位 0 表示正,1 表示负;数值位就是正常的 0/1 序列。

原码:直接将原数据按照正负数转换成二进制。
反码:原码的符号位不变,其他位依次按位取反。
补码:反码 +1.

2. 整型数据在内存中的存储

int 类型的 10,与 int 类型的 -10 在内存中的存储如下:

1-3

从上面的图上看,我们可以得出结论:

① 整型数据存放内存中的是二进制补码。
② 正整数的原、反、补码是相同的;但负整数的原、反、补码则需要计算。
③ printf 格式化输出的是数据的原码。

3. 为什么整型数据存在内存中存储的是补码

注意: CPU只有加法器,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。这样一来,使用补码,则可以将符号位和数值域统一处理。

我们就拿 1 + (-1) = 0 来举例:

// 1 - 1 <==> 1 + (-1)

 00000000 00000000 00000000 00000001	-> 1的原、反、补码

 10000000 00000000 00000000 00000001	-> -1的原码
 11111111 11111111 11111111 11111110	-> -1的反码
 11111111 11111111 11111111 11111111	-> -1的补码
// 错误的算法(使用原码相加)

 00000000 00000000 00000000 00000001	-> 1的原码
 +
 10000000 00000000 00000000 00000001	-> -1的原码
 10000000 00000000 00000000 00000010	-> 数值为 -2

// 正确的算法(使用补码相加)

 00000000 00000000 00000000 00000001	-> 1的补码
 +
 11111111 11111111 11111111 11111111	-> -1的补码
100000000 00000000 00000000 00000000	-> 数值为 0(最前面的1 舍去)

从结果来看,CPU 加法器对原码直接运算产生的结果是错误的,而采用补码是正确的。

4. 有符号和无符号的数据类型

char
unsigned char
signed char

short <==> signed short		// 有符号短整型
unsigned short				// 无符号短整型

int <==> signed int			// 有符号整型
unsigned int				// 无符号整型

long <==> signed long 		// 有符号长整型
unsigned long				// 无符号长整型

注意事项:

① unsigned 代表无符号类型,signed 代表有符号类型。如果没有特殊说明,一般就表示有符号类型。( 例如:int 就等价于 signed int 类型,即有符号整型。short、long 也默认为是有符号类型。但 char 官方并没有说明默认是有符号类型,这取决编译器的实现。)

② 有符号类型的二进制最高位是符号位,无符号类型的二进制最高位依然是数据位。

有符号和无符号的存储范围

我们以 char 类型的有符号和无符号对比, char 类型是一个字节,即 8 个比特位。

1-4

通过上图分析,我们可以看到有符号 char 类型的数据存储范围:-128 ~ 127,而 无符号 char 类型的数据存储范围:0 ~ 255. 类比 short、int、long 类型的数据范围也是这么计算来的。

猜想

理解了上面的有符号和无符号原理后,如果我们将一个负数放进一个无符号类型中,那么结果会发生什么事情呢?

程序清单:

#include <stdio.h>

int main() {

	unsigned char a = -10;
	printf("%u\n", a); // %u 为无符号打印

	return 0;
}

// 输出结果:246

从输出结果来看,-10 依然是按照二进制补码 11110110 存储至内存中的,只是在最后输出的时候,程序将 -10 的补码视为 -10 的原码直接就打印出来了,因为无符号整型本身就是一个不存在负数的类型,所以程序就视为原、反、补码相同才输出的。

1-5

所以输出结果并不是 -10,而是直接将其视为无符号二进制计算出的结果。

1-6

5. 关于 char 类型

char 类型占用内存的大小为 1 个字节,即 8 个比特位。而我们一般普遍认为 char 是字符类型。但实际上字符类型在底层存储字符的时候,存储却是字符对应的 ASCII 码值,所以我们依然可以将 char 类型视为整型。

字符 ’ A ’ 在内存中存储的二进制补码如下所示:实际上 ’ A ’ 的 ASCII 码值为 65,系统再转换成对应的二进制序列放入了内存中,为了方便显示,以十六进制显示在我们的面前。

1-7

三、大小端存储

1-8

在上图,我们可以看到局部变量 a,在内存中存储的倒序的字节数据。这是为什么呢?其实这是在 VS 底下的编译器,它采用的是小端存储模式。

注意: 计算机在内存中存储数据是二进制序列,但是 VS 编译器为了方便我们观察,采用了十六进制显示序列,2个十六进制位对应 8个二进制位,对应1个字节.

1. 两种存储方式的区别

大端存储方式:数据的低位字节保存在内存的高地址中,而数据的高位字节保存在内存的低地址中。

小端存储方式:数据的低位字节保存在内存的低地址中,而数据的高位字节保存在内存的高地址中。

1-8

2. 设计一个程序来判断当前编译器的字节序存储

设计思路:利用 char* 指针来找到整个数据的第一个字节,从而判断是否为对应的字节值即可。( 例如:0x 00 00 00 01,如果是小端存储,必然第一个字节取出的是低位 01;反之如果是大端存储,必然第一个字节取出的是高位 00. )

注意: 利用指针访问、解引用都是从低地址往高地址操作的。

1-9

程序清单:

#include <stdio.h>

int main() {

	int a = 0x00000001;
	char* p = (char*) & a;

	if (*p == 1) {
		printf("小端存储\n");
	}else {
		printf("大端存储\n");
	}

	return 0;
}

四、整型提升

由于表达式的整型运算需要在 CPU 的相应运算器件内执行,而 CPU 内整型运算器(ALU) 的操作数的字节长度一般是 int 类型的字节长度,同时也是 CPU 的通用寄存器的长度。因此,即使两个 char 类型的变量相加,在 CPU 执行时也要先转换为 CPU 内整型操作数的标准长度。所以,表达式中各种长度可能小于 int 长度的整型值,都必须先转换为 int 或 unsigned int,然后才能送入 CPU 去执行运算。

注意:

① 对于有符号类型,整形提升是按二进制最高位补全。
② 对于无符号类型,整型提升直接按 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);
	return 0;
}

// 输出结果:-1 -1 255

运算过程:

① 变量 a 的运算过程 (变量 b 也是同理)

// 1. 将 -1 数据放入变量 a 的内存中
10000000 00000000 00000000 00000001		-> -1 原码
11111111 11111111 11111111 11111110		-> -1 反码
11111111 11111111 11111111 11111111		-> -1 补码

11111111 // 截断成 char 类型,放入变量 a 中(补码)

// 2. 整型提升,由于变量 a 是有符号类型,所以按最高位补全
11111111 11111111 11111111 11111111		-> 新的补码

// 3. %d 打印,输出一个有符号的整型数据
11111111 11111111 11111111 11111111		-> 新的补码 
11111111 11111111 11111111 11111110		-> 新的反码 
10000000 00000000 00000000 00000001		-> 新的原码 (最终输出 -1)

② 变量 c 的运算过程

// 1. 将 -1 数据放入变量 c 的内存中
10000000 00000000 00000000 00000001		-> -1 原码
11111111 11111111 11111111 11111110		-> -1 反码
11111111 11111111 11111111 11111111		-> -1 补码

11111111 // 截断成 char 类型,放入变量 c 中(补码)

// 2. 整型提升,由于变量 c 是无符号类型,所以按 0 补全
00000000 00000000 00000000 11111111		-> 新的原、反、补码

// 3. %u 打印,输出一个无符号的整型数据
00000000 00000000 00000000 11111111		-> 不存在负数,直接输出 255

程序清单2

#include <stdio.h>

int main() {

	char a = 3;
	char b = 127;
	char c = a + b;
	printf("%d\n", c);

	return 0;
}

// 输出结果:-126

计算过程:

// 1. 将 a + b 的结果数据放入变量 c 的内存中
00000000 00000000 00000000 00000011		-> 3 的原、反、补码
00000011 // 截断成 char 类型,放入变量 a 中(补码)

00000000 00000000 00000000 01111111		-> 127 的原、反、补码
01111111 // 截断成 char 类型,放入变量 b 中(补码)

// 2. 整型提升,由于变量 a, b 是有符号类型,所以按最高位补全
00000000 00000000 00000000 00000011		-> 3 的补码
+
00000000 00000000 00000000 01111111		-> 127 的补码
=
00000000 00000000 00000000 10000010		-> 新的补码

10000010	// a + b 的结果,截断成 char 类型,放入变量 c 中(补码)

// 3. 整型提升,由于变量 c 是有符号类型,所以按最高位补全
11111111 11111111 11111111 10000010		-> 新的补码

// 4. %d 打印,输出一个有符号的整型数据
11111111 11111111 11111111 10000010		-> 新的补码
11111111 11111111 11111111 10000001		-> 新的反码
10000000 00000000 00000000 01111110		-> 新的原码 (最终输出 -126)

程序清单3

#include <stdio.h>

int main()
{
	char a = -128;
	char b = 128;
	printf("%u %u\n", a, b); 

	return 0;
}

// 输出结果:4294967168  4294967168

运算过程:变量 a

// 1. 将 -128 数据放入变量 a 的内存中
10000000 00000000 00000000 10000000		-> -128 原码
11111111 11111111 11111111 01111111		-> -128 反码
11111111 11111111 11111111 10000000		-> -128 补码

10000000 // 截断成 char 类型,放入变量 a 中(补码)

// 2. 整型提升,由于变量 a 是有符号类型,所以按最高位补全
11111111 11111111 11111111 10000000

// 3. %u 打印,输出一个无符号的整型数据
11111111 11111111 11111111 10000000		-> 不存在负数,直接输出 4294967168

运算过程:变量 b

// 1. 将 128 数据放入变量 b 的内存中
00000000 00000000 00000000 10000000		-> 128 原、反、补码

10000000 //截断成 char 类型,放入变量 b 中(补码)

// 2. 整型提升,由于变量 b 是有符号类型,所以按最高位补全
11111111 11111111 11111111 10000000		-> 新的补码

// 3. %u 打印,输出一个无符号的整型数据
11111111 11111111 11111111 10000000		-> 不存在负数,直接输出 4294967168

程序清单4

#include <stdio.h>

int main() {

	int i = -20;
	unsigned int j = 10;
	printf("%d\n", i + j);

	return 0;
}

运算过程:

10000000 00000000 00000000 00010100		-> -20 原码
11111111 11111111 11111111 11101011		-> -20 反码
11111111 11111111 11111111 11101100		-> -20 补码

00000000 00000000 00000000 00001010		-> 10 原、反、补码

// 1. i + j 的计算过程
11111111 11111111 11111111 11101100		-> -20 补码
+
00000000 00000000 00000000 00001010		-> 10 补码
=
11111111 11111111 11111111 11110110		-> 新的补码

// 2. %d 打印,输出一个有符号的整型数据
11111111 11111111 11111111 11110110		-> 新的补码
11111111 11111111 11111111 11110101		-> 新的反码
10000000 00000000 00000000 00001010 	-> 新的原码 (最终输出 -10)

程序清单5

#include <stdio.h>
#include <Windows.h>

int main() {

	unsigned int i;
	for (i = 9; i >= 0; i--)
	{
		printf("%u\n", i);
		Sleep(1000); // 休眠 1 秒
	}

	return 0;
}

// 输出结果:9  8  7  6  5  4  3  2  1  0  4294967295  4294967294...

运算过程:

00000000 00000000 00000000 00001001		-> 9 的原、反、补码
...
10000000 00000000 00000000 00000001		-> - 1 原码
11111111 11111111 11111111 11111110		-> - 1 反码
11111111 11111111 11111111 11111111		-> - 1 补码 (4,294,967,295)

// 当 -1 放入无符号变量 i 中时,此时程序就不将其视为负数了,
// 所以最终将其视为无符号直接输出,即所有的二进制补码序列全是数据位

程序清单6

#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\n", strlen(a));

	return 0;
}

// 输出结果:255

首先,我们得明白 strlen 是用来求字符串的长度的 ( ’ \0 ’ 之前的),而 ’ \0 ’ 的 ASCII 码也是 0. 所以对于上面的程序,当字符数组中间的元素出现 字符0 时, 就意味着 strlen 函数计算字符串长度已经到头了。

计算过程:

截断发生的过程:

// i = 0
10000000 00000000 00000000 00000001		-> -1 原码
11111111 11111111 11111111 11111110		-> -1 反码
11111111 11111111 11111111 11111111		-> -1 补码

11111111 // 截断放入 a[0] 中(补码)

// i = 1
10000000 00000000 00000000 00000010		-> -2 原码
11111111 11111111 11111111 11111101		-> -2 反码
11111111 11111111 11111111 11111110		-> -2 补码

11111110 // 截断放入 a[1] 中(补码)

// i = 2
10000000 00000000 00000000 00000011		-> -3 原码
11111111 11111111 11111111 11111100		-> -3 反码
11111111 11111111 11111111 11111101		-> -3 补码

11111101 // 截断放入 a[2] 中(补码)
...
...

经过上面的分析,可以总结出,字符数组中第 256 位被放入的是 0,所以在第 256 位之前,就是应该 strlen 所计算的字符串长度。

// 字符数组中每个字符存储的二进制补码
11111111		-> -1 补码
11111110		-> -2 补码
11111101		-> -3 补码
...
...
00000001		
00000000 // 第 256 位补码

或者我们也可以如下分析:

无符号的 char 类型的范围:-128 ~ 127

1-10

程序清单7

#include <stdio.h>
#include <Windows.h>

unsigned char i = 0;
int main()
{
	for (i = 0; i <= 255; i++)
	{
		printf("hello world, %d\n", i);
		Sleep(100); // 休眠 0.1 秒
	}
	return 0;
}

// 输出结果:0 - 255, 0 - 255, 0 - 255 ...

运算过程:

有符号的 char 类型的范围:0 ~ 255

 00000000			-> 0 补码
 00000001			-> 1 补码
 ...
 ...
 11111111			-> 255 补码
100000000			-> 256 补码	-> 截断成 00000000
 00000001			-> 1 补码
 ...
 ...

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java项目:SSH企业人力资源管理系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 企业人力资源管理系统&#xff0c;分为超级管理员与普通管理员两种角色,超级管理员可以对普通管理员进行添加、删除等操作&#xff1b; 超级管理…

【k8s】1、基础概念和架构及组件

文章目录一、kubernetes概述1、什么是kubernetes&#xff1f;2、应用程序部署方式的演变3、为什么要用kubernetes&#xff1f;二、kubernetes 特性三、Kubernetes集群架构与核心组件1、master组件1.1 kube-apiserver&#xff08;中央枢纽&#xff09;1.2 kube-controller-manag…

股价下跌18%后,满帮集团(YMM)的财务业绩正在加速放缓

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 市场与竞争 根据Beroe Advantage Procuremen的一份市场研究报告&#xff0c;2020年中国道路运输服务市场的规模估计为250亿美元。 这意味着这个行业近年来的复合年增长率为6.5%。 物流参与者往往集中在中国的西南和中南部地…

.NET跨平台框架选择之一 - Avalonia UI

1. Avalonia UI简介 Avalonia UI文档教程&#xff1a;https://docs.avaloniaui.net/docs/getting-started 随着跨平台越来越流行&#xff0c;.NET支持跨平台至今也有十几年的光景了(Mono开始)。 但是目前基于.NET的跨平台&#xff0c;大多数还是在使用B/S架构的跨平台上&…

数据分析面试重点

2022年10月求职季&#xff0c;疫情的影响&#xff0c;但是也挡不住各位小伙伴&#xff0c;找工作的热情。目前&#xff0c;数据分析行业大火&#xff0c;相信很多小伙伴都想去这一行业试试水。想要成功进入数据分析行业&#xff0c;就必须得通过数据分析面试&#xff0c;面试的…

计控实验(二)——积分分离PID控制实验

太原理工大学计算机控制技术实验之积分分离PID控制实验 积分分离PID控制实验实验原理实验内容实验结果思考题实验原理 上图是一个典型的PID 闭环控制系统方框图&#xff0c;其硬件电路原理及接线图可设计如下&#xff0c;图中画“○”的线需用户在实验中自行接好&#xff0c;对…

跟艾文学编程《Python基础》PyCharm 安装

作者&#xff1a;艾文&#xff0c;计算机硕士学位&#xff0c;企业内训讲师和金牌面试官&#xff0c;公司资深算法专家&#xff0c;现就职BAT一线大厂。 邮箱&#xff1a;1121025745qq.com 博客&#xff1a;https://edu.csdn.net/lecturer/894?spm1003.2001.3001.4144 内容&am…

[附源码]java毕业设计文具销售系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

6、子查询 、having、(not) exists、表中增加一列并补充数据、删除表中对应的数据-mysql

目录子查询一、需求分析与问题解决1、实际问题2、子查询的基本使用3、子查询的分类二、单行子查询1、单行比较操作符2、 代码示例3、HAVING 中的子查询4、CASE中的子查询5、 子查询中的空值问题6、 非法使用子查询三、多行子查询1、多行比较操作符2、代码示例3、空值问题四、 相…

《Flowable流程引擎从零到壹》Flowable流程引擎介绍和实战项目初始化流程引擎实例

14天学习训练营导师课程&#xff1a; 邓澎波《Flowable流程引擎-基础篇【2022版】》 邓澎波《Flowable流程引擎-高级篇【2022版】》 学习笔记《Flowable流程引擎从零到壹》回城传送 ❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。Java领域优质创…

mysql中的这些日志,你都知道吗?

在使用mysql的过程中&#xff0c;经常会听到mysql具有数据恢复能力&#xff0c;当我们在业务开发中误删了某些数据后&#xff0c;可以将数据库恢复到误删之前的状态。同时还具有故障恢复能力&#xff0c;当数据库所在的机器突然掉电停机后&#xff0c;mysql也可以保证数据一致性…

Java8 Stream 的核心秘密

小伙伴们好呀&#xff0c;我是 4ye&#xff0c;今天来分享下 Java8 Stream 的源码 核心回顾 stream 是一次性的&#xff0c;不是数据结构&#xff0c;不存储数据&#xff0c;不改变源数据.。API 分为终端和中间操作&#xff0c;中间操作是惰性的&#xff0c;碰到终端才去执行。…

python自动化测试

测试软件&#xff1a;pycharm 解释器版本&#xff1a;3.10.7 测试浏览器&#xff1a;谷歌 或 edge 谷歌web驱动下载地址&#xff1a;chromedriver.storage.googleapis.com/index.html edgeweb驱动下载地址&#xff1a;Microsoft Edge WebDriver - Microsoft Edge Developer 第一…

免费查题接口系统后台

免费查题接口系统后台 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xff08;点…

零基础自学javase黑马课程第十三天

零基础自学javase黑马课程第十三天 ✨欢迎关注&#x1f5b1;点赞&#x1f380;收藏⭐留言✒ &#x1f52e;本文由京与旧铺原创&#xff0c;csdn首发&#xff01; &#x1f618;系列专栏&#xff1a;java学习 &#x1f4bb;首发时间&#xff1a;&#x1f39e;2022年11月7日&…

springboot视图渲染技术

目录 一、Freemarker简介及入门 二、Freemarker语法 1、取值 1.1默认值 1.2 对null值进行判断 2、条件 3、循环 4、include 5、 局部变量(assign)/全局变量(global) 一、Freemarker简介及入门 学习网址&#xff1a;什么是 FreeMarker? - FreeMarker 中文官方参考手册 …

2022强网拟态pwn-webheap

2022强网拟态pwn-webheap 这题的逻辑是一开始笔者硬逆给逆出来了&#xff0c;但是后面在Ex师傅的点播下成功的在github上找到了这个的原生项目 https://github.com/google/libnop 在审计的过程中发现了至关重要的信息&#xff0c;发现和逆向的格式一样的东西&#xff0c;就是…

BCN-PEG2-Mal,BCN-OH,endo-BCN-PEG2-BiotinBCN几种衍生物的特点

BCN-PEG 是一类具有双环[6.1.0]壬炔的 PEG 接头。它用于叠氮化物反应性无铜点击化学。BCN在没有催化剂的情况下很容易与叠氮官能化化合物或生物分子反应形成稳定的三唑键&#xff0c;PEG间隔物增加了所得化合物的水溶性。今天这一章西安凯新生物科技有限公司​主要为大家分享BC…

多段曲线控温从Simulink仿真到PLC控制实现

多段曲线温度控制,专栏有系列文章和完整源代码,大家可以自行搜索。链接如下: 博途PLC 1200/1500PLC多段曲线控温FB(支持40段控温曲线、段曲线搜索、暂停、跳段等功能)_RXXW_Dor的博客-CSDN博客多段控温曲线之前专栏的其它文章也有所讲解,大家可以参看下面这篇专栏博途1200…

Get CLI - 强大的项目管理 GetX 工具

Get CLI - 强大的项目管理 GetX 工具 https://shirsh94.medium.com/get-cli-powerful-getx-tool-for-project-management-bdf54572d91f 前言 一个来自 Getx 的工具可以帮助你快速的建立项目&#xff0c;页面和视图。 大家好&#xff0c;在本文中&#xff0c;我将讨论 get CLI 工…