

本篇博客全站热榜排名:3


一、前言
在之前,我们学习了有关C语言中的各种数据类型以及它们的存储空间大小,如下图所示

类型的意义:
- 使用这个类型开辟内存空间的大小(大小决定了使用范围)
- 如何看待内存空间的视角
二、类型的基本归类
接下去我将上面的这些类型做一个分类,大致分为以下5类
1、整型家族
首先看到的是【整型家族】,分别有char、short、int、long

可能有些同学看到上面的这些很多类型有点懵,什么signed、unsigned,下面我就为你来先做一个解答🔍
👉char为何归到整型家族?
- 因为char在字符存储的时候存的是一个ASCLL码值,而ASCLL码值是一个整数
👉为什么有unsigned和signed两个不同的类型呢
- 因为数值有正数和负数之分
- 有些数值只有正数,没有负数(身高)—— unsigned
- 有些数值,有正数也有负数(温度)—— signed
👉子分类后面的[int]是什么?
- 因为像
short、long这些都是属于整型的范畴,其实应该写成【signed short int】和【unsigned short int】这样,只是为了简写忽略了后面的int
👉像[char]、[signed char]、[unsigned char]这些该如何区分?
- char 分为【char】、【signed char】、【unsigned char】
- short 分为【short == signed short】、【unsigned short】
- int 分为【int == signed int】
- long 分为【long == signed long】
2、浮点数家族
浮点数只分为两类,一个是【float】,一个则是【double】,这里只是做介绍,下文会专门介绍浮点数在内存中的存储

3、构造类型
有关构造类型的话就分为以下这四种,对于【结构体】、【枚举】、【联合】这里不再细说,会专门开章节叙述

- 主要的话是要提一嘴这个数组类型。例如看到下面的这三个数组,它们都是互不相同的,只要你修改了它的元素类型或者是元素个数,那这就是个不同的数组

4、指针类型
接下去是指针类型,对于int、char、float这三种类型的指针我们之前都见到过,但是可能有同学没有遇见过这个void类型的指针

- 它叫做【空指针】
- 对于int类型的指针可以用来接收int类型的数据的地址
- 对于char类型的指针可以用来接收char类型的数据的地址
- 对于float类型的指针可以用来接收float类型的数据的地址
- 对于
void类型的指针可以用来接收任何类型数据的地址【它就像一个垃圾桶一样,起到临时存放的作用】
5、空类型
void 表示空类型(无类型)
通常应用于函数的返回类型、函数的参数、指针类型
三、整型在内存中的存储【⭐】
接下去我们来聊聊有关整型的数据在内存中的存储形式
1、原码、反码、补码
对于原码、反码、补码来说我们之前在学习【操作符】的时候有遇到过,这么我们再来正式地介绍一下
① 概念介绍
计算机中的整数有三种2进制表示方法,即原码、反码和补码。
- 三种表示方法均有符号位和数值位两部分,符号位都是用
0表示“正”,用1表示“负”,而数值位- 正数的原、反、补码都相同
- 负整数的三种表示方法各不相同
接下去就来分别讲讲正数和负数的原、反、补码有什么不同
int a = 10;
- 对于正数说,因为原、反、补都是相同的,所以当我们写出其原码的时候,其实就可以得出它的反码和补码了

int a = -10;
- 对于负数来说就不太一样了,要得到反码就将原码除符号位外其余各位取反,要得到补码的话就在反码的基础上 + 1

其实除了这三种之外,还有一种叫做【移码】,如果你学习过《计算机组成原理》这门课应该就可以知道移码就是符号位与补码相反,数值位与补码相同。本文不过过多细究
② 原码与补码的转换形式总结
学习了概念后,我们来总结一下有关原码与补码的之间的转换
- 原码到补码 —— 1种方式
- 原码取反,+1得到补码
- 补码到原码 —— 2种方式
- 补码 - 1,取反得到原码
- 补码取反,+1得到原码
- 第1种很直观,我们主要来说说第二种,也就是将补码取反,+1得到原码,回想原码是怎么到补码的,其实你也就学会了补码怎么转换回原码的,只是这一种转换方式大家可能没有怎听说过

③ 探究计算机内部的存储编码
上面说到了三种整型编码方式,但是真正到了计算机内部使用的是哪个呢?
对于整形来说:数据存放内存中其实存放的是补码。
- 通过去VS中进行调试观察【调试】- 【窗口】- 【内存】就可以看到其实在内存中是以补码的形式存放的

- 但是有同学说:这个
f6 ff ff ff是啥呀,怎么就补码了?通过看前面的内存地址可以发现这其实是16进制的表示方式,若是以32位2进制来进行存放的话就太长了,所以采取十六进制的形式 - 在【进制转换】中,4位二进制表示1位16进制。通过将补码4位4位进行一个划分就可以得出8个16进制的数字为
ff ff ff f6,但是仔细一看却可以发现这和VS中我们所观察的结果有所不同,感觉倒了一下【这就要涉及到我们下面所要将的大小端存储】

但是你有疑惑过在计算机内部要以【补码】的形式进行存放,而不是以原码的形式存放呢?
- 因为其实很简单,虽然原码的表示形式简单易懂【只需要将真值的+ - 号转换为01即可】,但是原码的加法却异常复杂,需要考虑到两数是同号还是异号以及其他复杂的问题,所以为了解决这些矛盾,人们找到了补码表示法
- 在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理
- 同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路
- 其运算过程是相同的其实也就印证了我上面介绍的补码转换为原码的第二种方式
- 虽然在计算机内部是以补码的形式进行存储,但是当其与我们进行交互的时候使用的却是原码的形式。
- 可能也有同学会疑惑上面的第二点讲【加法和减法也可以统一处理】,我们通过一个最简单的例子就是两数相加
+来看看
int a = 1;
int b = -1;
int c = a + b;
printf("c = %d\n", c);
- 首先,我们写出a与b的补码,因为在内存中要以补码的形式进行存放和计算
int a = 1;
00000000 00000000 00000000 00000001 - 原/反/补码
int b = -1;
10000000 00000000 00000000 00000001 - 原码
11111111 11111111 11111111 11111110 - 反码
11111111 11111111 11111111 11111111 - 补码
- 接下去对这两个补码进行相加,因为二进制逢二进一,所以可以看到最后进位开头多出了一位
int c = a + b;
00000000 00000000 00000000 00000001
11111111 11111111 11111111 11111111
---------------------------------------------
100000000 00000000 00000000 00000000
- 但是呢,因为c为int类型的整数,所以只能存的下4个字节,也就是32个比特位的数据,所以将最高位【截断】之后剩下的32位全为0
100000000 00000000 00000000 00000000
00000000 00000000 00000000 00000000 - 整型只能存放4B,32b
- 所以可以得出最后的答案为0。在内存中
1 - 1 = 0是这样计算的,你明白了吗?

【总结一下】:
- 内存中存放的都是补码
- 整型表达式计算使用的内存中补码计算的
- 打印和我们看到的都是原码
2、大小端介绍【补码存储的顺序】
① 大小端的由来
我们在开始可以先看这样一个故事
有两个特别强大的国家在过去进行了36个月的战争,在这期间发生了件事情,就是吃鸡蛋的时候,原始的方法是打破鸡蛋较大的一端,可那时的皇帝的祖父由于小时侯吃鸡蛋,按这种方法把手指弄破了,因此他的父亲,就下令,命令所有的子民吃鸡蛋的时候,必须先打破鸡蛋较小的一端,违令者重罚。然后老百姓对此法令极为反感,期间发生了多次叛乱,其中一个皇帝因此送命,另一个丢了王位,产生叛乱的原因就是另一个国家Blefuscu的国王大臣煽动起来的,叛乱平息后,就逃到这个帝国避难。据估计,先后几次有11000余人情愿死也不肯去打破鸡蛋较小的端吃鸡蛋。这个其实讽刺当时英国和法国之间持续的冲突。Danny Cohen一位网络协议的开创者,第一次使用这两个术语指代字节顺序,后来就被大家广泛接受,这个就是关于大端小端名词的由来
- 看完后可以发现,原来大小端的由来就是因为鸡蛋🥚要从哪头剥引起的
② 为什么要有大端和小端之分?
在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为8 bit
- 上面我们介绍过很多的数据类型,有【char】【int】【double】等等,不过除了8bit的char之外,还有16 bit的short型,32 bit的long型(要看具体的编译器),这些数据类型所定义的数值在内存中存放的都超过了1个字节了,要存储到内存中,就有导致一个顺序问题
- 因为在内存中我们是以字节为单位来讨论数据的存放,就好像下面这个
0x12345678在内存中12为1个字节,34为一个字节,56为一个字节,78为一个字节,所以通过右侧的【内存】我们就可以看出虽然呈现的是一个倒着存放样子,但是呢并不是完全倒着,像87 65 43 21,而是78 56 34 12。这就是因为它们整体作为一个字节,讨论的是每个字节顺序,而不是每个字节内部的顺序

这,也就导致了【大端】和【小端】的由来,接下去呢就正式地来给读者介绍一下这种倒着存放的方式
③ 大(小)端字节序存储
首先来看一下它们的概念,这至关重要⭐
- 【大端(存储)模式】:是指数据的
低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中; - 【小端(存储)模式】:是指数据的
低位保存在内存的低地址中,而数据的高位,,保存在内存的高地址中;
- 可以看到,对于下面这一个十六进制数
0x11223344,以进制的权重来看的话右边的权重低【0】,左边的权重高【3】,所以11为高位,44为低位。所以若是对其进行小端字节存储的话就要将44存放到低位,11存放到高位,这也就印证了为什么我们最后在看到内存中的存放是

本文详细介绍了C语言中整型和浮点型数据在内存中的存储方式,包括原码、反码、补码的概念和转换,以及大小端存储模式。文章通过实例分析了有符号和无符号数据的范围,探讨了整型提升和浮点数的存储规则,并提供了多道经典笔试题来巩固理解。
最低0.47元/天 解锁文章
26





