--- comments: true --- # 3.1. 数据与内存 ## 3.1.1. 基本数据类型 谈到计算机中的数据,我们能够想到文本、图片、视频、语音、3D 模型等等,这些数据虽然组织形式不同,但都是由各种基本数据类型构成的。 **「基本数据类型」是 CPU 可以直接进行运算的类型,在算法中直接被使用**。 - 「整数」根据不同的长度分为 byte, short, int, long ,根据算法需求选用,即在满足取值范围的情况下尽量减小内存空间占用; - 「浮点数」代表小数,根据长度分为 float, double ,同样根据算法的实际需求选用; - 「字符」在计算机中是以字符集的形式保存的,char 的值实际上是数字,代表字符集中的编号,计算机通过字符集查表来完成编号到字符的转换。占用空间与具体编程语言有关,通常为 2 bytes 或 1 byte ; - 「布尔」代表逻辑中的“是”与“否”,其占用空间需要具体根据编程语言确定,通常为 1 byte 或 1 bit ;
Fig. IEEE 754 标准下的 float 表示方式
以上图为例,$\mathrm{S} = 0$ , $\mathrm{E} = 124$ ,$\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ ,易得 $$ \text { val } = (-1)^0 \times 2^{124 - 127} \times (1 + 0.375) = 0.171875 $$ 现在我们可以回答开始的问题:**float 的表示方式包含指数位,导致其取值范围远大于 int** 。根据以上计算, float 可表示的最大正数为 $2^{254 - 127} \times (2 - 2^{-23}) \approx 3.4 \times 10^{38}$ ,切换符号位便可得到最小负数。 **浮点数 float 虽然拓展了取值范围,但副作用是牺牲了精度**。整数类型 int 将全部 32 位用于表示数字,数字是均匀分布的;而由于指数位的存在,浮点数 float 的数值越大,相邻两个数字之间的差值就会趋向越大。 进一步地,指数位 $E = 0$ 和 $E = 255$ 具有特殊含义,**用于表示零、无穷大、$\mathrm{NaN}$ 等**。 | 指数位 E | 分数位 $\mathrm{N} = 0$ | 分数位 $\mathrm{N} \ne 0$ | 计算公式 | | ------------------ | ----------------------- | ---------------------------- | ------------------------------------------------------------ | | $0$ | $\pm 0$ | 次正规数(subnormal number) | $(-1)^{\mathrm{S}} \times 2^{-126} \times (0.\mathrm{N})$ | | $1, 2, \dots, 254$ | 正规数 | 正规数 | $(-1)^{\mathrm{S}} \times 2^{(\mathrm{E} -127)} \times (1.\mathrm{N})$ | | $255$ | $\pm \infty$ | $\mathrm{NaN}$ | | 特别地,次正规数显著提升了小数精度: - 最小正正规数为 $2^{-126} \approx 1.18 \times 10^{-38}$ ; - 最小正次正规数为 $2^{-126} \times 2^{-23} \approx 1.4 \times 10^{-45}$ ; 双精度 double 也采用类似 float 的表示方法,在此不再赘述。 ### 基本数据类型与数据结构的关系 我们知道,**数据结构是在计算机中组织与存储数据的方式**,它的主语是“结构”,而不是“数据”。如果我们想要表示“一排数字”,自然想到使用「数组」数据结构。数组的存储方式可以表示数字的相邻关系、顺序关系,但至于其中存储的是整数 int ,还是小数 float ,或是字符 char ,**则与所谓的数据的结构无关了**。 换言之,基本数据类型提供了数据的“内容类型”,而数据结构提供数据的“组织方式”。 === "Java" ```java title="" /* 使用多种「基本数据类型」来初始化「数组」 */ int[] numbers = new int[5]; float[] decimals = new float[5]; char[] characters = new char[5]; boolean[] booleans = new boolean[5]; ``` === "C++" ```cpp title="" /* 使用多种「基本数据类型」来初始化「数组」 */ int numbers[5]; float decimals[5]; char characters[5]; bool booleans[5]; ``` === "Python" ```python title="" """ Python 的 list 可以自由存储各种基本数据类型和对象 """ list = [0, 0.0, 'a', False] ``` === "Go" ```go title="" // 使用多种「基本数据类型」来初始化「数组」 var numbers = [5]int{} var decimals = [5]float64{} var characters = [5]byte{} var booleans = [5]bool{} ``` === "JavaScript" ```javascript title="" /* JavaScript 的数组可以自由存储各种基本数据类型和对象 */ const array = [0, 0.0, 'a', false]; ``` === "TypeScript" ```typescript title="" /* 使用多种「基本数据类型」来初始化「数组」 */ const numbers: number[] = []; const characters: string[] = []; const booleans: boolean[] = []; ``` === "C" ```c title="" /* 使用多种「基本数据类型」来初始化「数组」 */ int numbers[10]; float decimals[10]; char characters[10]; bool booleans[10]; ``` === "C#" ```csharp title="" /* 使用多种「基本数据类型」来初始化「数组」 */ int[] numbers = new int[5]; float[] decimals = new float[5]; char[] characters = new char[5]; bool[] booleans = new bool[5]; ``` === "Swift" ```swift title="" /* 使用多种「基本数据类型」来初始化「数组」 */ let numbers = Array(repeating: Int(), count: 5) let decimals = Array(repeating: Double(), count: 5) let characters = Array(repeating: Character("a"), count: 5) let booleans = Array(repeating: Bool(), count: 5) ``` === "Zig" ```zig title="" ``` ## 3.1.2. 计算机内存 在计算机中,内存和硬盘是两种主要的存储硬件设备。「硬盘」主要用于长期存储数据,容量较大(通常可达到 TB 级别)、速度较慢。「内存」用于运行程序时暂存数据,速度较快,但容量较小(通常为 GB 级别)。 **算法运行中,相关数据都被存储在内存中**。下图展示了一个计算机内存条,其中每个黑色方块都包含一块内存空间。我们可以将内存想象成一个巨大的 Excel 表格,其中每个单元格都可以存储 1 byte 的数据,在算法运行时,所有数据都被存储在这些单元格中。 **系统通过「内存地址 Memory Location」来访问目标内存位置的数据**。计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。 ![内存条、内存空间、内存地址](data_and_memory.assets/computer_memory_location.png)Fig. 内存条、内存空间、内存地址
**内存资源是设计数据结构与算法的重要考虑因素**。内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。