From b0f47574ac0dc86d48d6f18baefd90ff6fd52faa Mon Sep 17 00:00:00 2001 From: kazeno Date: Wed, 21 May 2025 20:09:26 +0800 Subject: [PATCH] wip --- src/ch08-00-common-collections.md | 13 ++++--- src/ch08-01-vectors.md | 25 +++++++------ src/ch08-02-strings.md | 58 ++++++++++++++----------------- src/ch08-03-hash-maps.md | 11 +++--- 4 files changed, 50 insertions(+), 57 deletions(-) diff --git a/src/ch08-00-common-collections.md b/src/ch08-00-common-collections.md index 3bf9d11..9a02952 100644 --- a/src/ch08-00-common-collections.md +++ b/src/ch08-00-common-collections.md @@ -1,14 +1,13 @@ # 常见集合 -> [ch08-00-common-collections.md](https://github.com/rust-lang/book/blob/main/src/ch08-00-common-collections.md) ->
-> commit 1fd890031311612e54965f7f800a8c8bd4464663 + + -Rust 标准库中包含一系列被称为 **集合**(*collections*)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。每种集合都有着不同功能和成本,而根据当前情况选择合适的集合,这是一项应当逐渐掌握的技能。在这一章里,我们将详细的了解三个在 Rust 程序中被广泛使用的集合: +Rust 标准库中包含一系列被称为 **集合**(*collections*)的非常有用的数据结构。大部分其他数据类型都代表一个特定的值,不过集合可以包含多个值。不同于内建的数组和元组类型,这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。每种集合都有着不同功能和开销,而根据当前情况选择合适的集合,这是一项需要逐渐掌握的技能。在这一章里,我们将详细的了解三个在 Rust 程序中被广泛使用的集合: -* *vector* 允许我们一个挨着一个地储存一系列数量可变的值 -* **字符串**(*string*)是字符的集合。我们之前见过 `String` 类型,不过在本章我们将深入了解。 -* **哈希 map**(*hash map*)允许我们将值与一个特定的键(key)相关联。这是一个叫做 *map* 的更通用的数据结构的特定实现。 +- **向量**(*vector*) 允许我们一个挨着一个地储存一系列数量可变的值 +- **字符串**(*string*)是字符的集合。我们之前见过 `String` 类型,不过在本章我们将深入了解。 +- **哈希 map**(*hash map*)允许我们将值与一个特定的键(key)相关联。这是一个叫做 *map* 的更通用的数据结构的特定实现。 对于标准库提供的其他类型的集合,请查看[文档][collections]。 diff --git a/src/ch08-01-vectors.md b/src/ch08-01-vectors.md index da2fb28..42e7792 100644 --- a/src/ch08-01-vectors.md +++ b/src/ch08-01-vectors.md @@ -1,20 +1,19 @@ ## 使用 Vector 储存列表 -> [ch08-01-vectors.md](https://github.com/rust-lang/book/blob/main/src/ch08-01-vectors.md) ->
-> commit ac16184a7f56d17daa9c4c76901371085dc0ac43 + + -我们要讲到的第一个类型是 `Vec`,也被称为 _vector_。vector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。 +我们要讲到的第一个类型是 `Vec`,也被称为 *vector*。vector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。 ### 新建 vector -为了创建一个新的空 vector,可以调用 `Vec::new` 函数,如示例 8-1 所示: +为了新建一个的空 vector,可以调用 `Vec::new` 函数,如示例 8-1 所示。 ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-01/src/main.rs:here}} ``` -示例 8-1:新建一个空的 vector 来储存 `i32` 类型的值 +示例 8-1:新建一个的空 vector 来储存 `i32` 类型的值 注意这里我们增加了一个类型注解。因为没有向这个 vector 中插入任何值,Rust 并不知道我们想要储存什么类型的元素。这是一个非常重要的点。vector 是用泛型实现的,第十章会涉及到如何对你自己的类型使用它们。现在,所有你需要知道的就是 `Vec` 是一个由标准库提供的类型,它可以存放任何类型,而当 `Vec` 存放某个特定类型时,那个类型位于尖括号中。在示例 8-1 中,我们告诉 Rust `v` 这个 `Vec` 将存放 `i32` 类型的元素。 @@ -62,17 +61,17 @@ Rust 提供了两种引用元素的方法的原因是当尝试使用现有元素 示例 8-5:尝试访问一个包含 5 个元素的 vector 的索引 100 处的元素 -当运行这段代码,你会发现对于第一个 `[]` 方法,当引用一个不存在的元素时 Rust 会造成 panic。这个方法更适合当程序认为尝试访问超过 vector 结尾的元素是一个严重错误的情况,这时应该使程序崩溃。 +当运行这段代码,你会发现对于第一个 `[]` 方法,当引用一个不存在的元素时 Rust 会造成 panic。此方法适用于当你希望在尝试访问 vector 末尾之外的元素时让程序直接崩溃的场景。 当 `get` 方法被传递了一个数组外的索引时,它不会 panic 而是返回 `None`。当偶尔出现超过 vector 范围的访问属于正常情况的时候可以考虑使用它。接着你的代码可以有处理 `Some(&element)` 或 `None` 的逻辑,如第六章讨论的那样。例如,索引可能来源于用户输入的数字。如果它们不慎输入了一个过大的数字那么程序就会得到 `None` 值,你可以告诉用户当前 vector 元素的数量并再请求它们输入一个有效的值。这就比因为输入错误而使程序崩溃要友好的多! -一旦程序获取了一个有效的引用,借用检查器将会执行所有权和借用规则(第四章讲到)来确保 vector 内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。这个规则适用于示例 8-6,当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候,如果尝试在函数的后面引用这个元素是行不通的: +一旦程序获取了一个有效的引用,借用检查器将会执行所有权和借用规则(第四章讲到)来确保 vector 内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。这个规则适用于示例 8-6,当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候,如果尝试在函数的后面再次引用这个元素是行不通的: ```rust,ignore,does_not_compile {{#rustdoc_include ../listings/ch08-common-collections/listing-08-06/src/main.rs:here}} ``` -示例 8-6:在拥有 vector 中项的引用的同时向其增加一个元素 +示例 8-6:尝试在拥有 vector 中项的引用的同时向其增加一个元素 编译会给出这个错误: @@ -102,15 +101,15 @@ Rust 提供了两种引用元素的方法的原因是当尝试使用现有元素 示例 8-8:遍历 vector 中元素的可变引用 -为了修改可变引用所指向的值,在使用 `+=` 运算符之前必须使用解引用运算符(`*`)获取 `i` 中的值。第十五章的 [“通过解引用运算符追踪指针的值”][deref] 部分会详细介绍解引用运算符。 +为了修改可变引用所指向的值,在使用 `+=` 运算符之前必须使用解引用运算符(`*`)获取 `i` 中的值。第十五章的[“通过解引用运算符追踪指针的值”][deref]部分会详细介绍解引用运算符。 -因为借用检查器的规则,无论可变还是不可变地遍历一个 vector 都是安全的。如果尝试在示例 8-7 和 示例 8-8 的 `for` 循环体内插入或删除项,都会得到一个类似示例 8-6 代码中类似的编译错误。`for` 循环中获取的 vector 引用阻止了同时对 vector 整体的修改。 +由于借用检查器的规则,无论可变还是不可变地遍历一个 vector 都是安全的。如果尝试在示例 8-7 和 示例 8-8 的 `for` 循环体内插入或删除项,都会得到一个类似示例 8-6 代码中类似的编译错误。`for` 循环中获取的 vector 引用阻止了同时对整个 vector 进行修改。 ### 使用枚举来储存多种类型 vector 只能储存相同类型的值。这是很不方便的;绝对会有需要储存一系列不同类型的值的用例。幸运的是,枚举的成员都被定义为相同的枚举类型,所以当需要在 vector 中储存不同类型值时,我们可以定义并使用一个枚举! -例如,假如我们想要从电子表格的一行中获取值,而这一行的有些列包含数字,有些包含浮点值,还有些是字符串。我们可以定义一个枚举,其成员会存放这些不同类型的值,同时所有这些枚举成员都会被当作相同类型:那个枚举的类型。接着可以创建一个储存枚举值的 vector,这样最终就能够储存不同类型的值了。示例 8-9 展示了其用例: +例如,假如我们想要从电子表格的一行中获取值,而这一行的有些列包含数字,有些包含浮点值,还有些是字符串。我们可以定义一个枚举,其成员会存放这些不同类型的值,同时所有这些枚举成员都会被当作相同类型:那个枚举的类型。接着可以创建一个储存该枚举值的 vector,这样最终就能够储存不同类型的值了。示例 8-9 展示了这个用法: ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-09/src/main.rs:here}} @@ -126,7 +125,7 @@ Rust 在编译时必须确切知道 vector 中的类型,这样它才能确定 ### 丢弃 vector 时也会丢弃其所有元素 -类似于任何其他的 `struct`,vector 在其离开作用域时会被释放,如示例 8-4 所标注的: +类似于任何其他的 `struct`,vector 在其离开作用域时会被释放,如示例 8-10 所标注的: ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-10/src/main.rs:here}} diff --git a/src/ch08-02-strings.md b/src/ch08-02-strings.md index f72d255..522da69 100644 --- a/src/ch08-02-strings.md +++ b/src/ch08-02-strings.md @@ -1,22 +1,21 @@ ## 使用字符串储存 UTF-8 编码的文本 -> [ch08-02-strings.md](https://github.com/rust-lang/book/blob/main/src/ch08-02-strings.md) ->
-> commit 668c64760b5c7ea654facb4ba5fe9faddfda27cc + + 第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解它。字符串是新晋 Rustacean 们通常会被困住的领域,这是由于三方面理由的结合:Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些要素结合起来对于来自其他语言背景的程序员就可能显得很困难了。 -在集合章节中讨论字符串的原因是,字符串就是作为字节的集合外加一些方法实现的,当这些字节被解释为文本时,这些方法提供了实用的功能。在这一部分,我们会讲到 `String` 中那些任何集合类型都有的操作,比如创建、更新和读取。也会讨论 `String` 与其他集合不一样的地方,例如索引 `String` 是很复杂的,由于人和计算机理解 `String` 数据方式的不同。 +在集合章节中讨论字符串的原因是,字符串就是作为字节的集合外加一些方法实现的,当这些字节被解释为文本时,这些方法提供了实用的功能。在本小节中,我们会讲到 `String` 中那些任何集合类型都有的操作,比如创建、更新和读取。也会讨论 `String` 与其他集合不一样的地方,例如索引 `String` 是很复杂的,由于人和计算机理解 `String` 数据方式的不同。 ### 什么是字符串? -在开始深入这些方面之前,我们需要讨论一下术语 **字符串** 的具体意义。Rust 的核心语言中只有一种字符串类型:字符串 slice `str`,它通常以被借用的形式出现,`&str`。第四章讲到了 **字符串 slices**:它们是一些对储存在别处的 UTF-8 编码字符串数据的引用。举例来说,由于字符串字面值被储存在程序的二进制输出中,因此字符串字面值也是字符串 slices。 +我们先定义一下**字符串**这一术语的具体意义。Rust 的核心语言中只有一种字符串类型,字符串 slice `str`,它通常以被借用的形式出现,`&str`。第四章讲到了**字符串 slices**:它们是一些对储存在别处的 UTF-8 编码字符串数据的引用。举例来说,由于字符串字面值被储存在程序的二进制输出中,因此它们也是字符串 slices。 字符串(`String`)类型由 Rust 标准库提供,而不是编入核心语言,它是一种可增长、可变、可拥有、UTF-8 编码的字符串类型。当 Rustaceans 提及 Rust 中的 "字符串 "时,他们可能指的是 `String` 或 string slice `&str` 类型,而不仅仅是其中一种类型。虽然本节主要讨论 `String`,但这两种类型在 Rust 的标准库中都有大量使用,而且 `String` 和 字符串 slices 都是 UTF-8 编码的。 ### 新建字符串 -很多 `Vec` 可用的操作在 `String` 中同样可用,事实上 `String` 被实现为一个带有一些额外保证、限制和功能的字节 vector 的封装。其中一个同样作用于 `Vec` 和 `String` 函数的例子是用来新建一个实例的 `new` 函数,如示例 8-11 所示。 +很多 `Vec` 上可用的操作在 `String` 中同样可用,事实上 `String` 被实现为一个带有一些额外保证、限制和功能的字节 vector 的封装。其中一个同样作用于 `Vec` 和 `String` 函数的例子是用来新建一个实例的 `new` 函数,如示例 8-11 所示。 ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-11/src/main.rs:here}} @@ -24,7 +23,7 @@ 示例 8-11:新建一个空的 `String` -这新建了一个叫做 `s` 的空的字符串,接着我们可以向其中装载数据。通常字符串会有初始数据,因为我们希望一开始就有这个字符串。为此,可以使用 `to_string` 方法,它能用于任何实现了 `Display` trait 的类型,比如字符串字面值。示例 8-12 展示了两个例子。 +这新建了一个叫做 `s` 的空的字符串,接着我们可以向其中加载数据。通常字符串会有初始数据,因为我们希望一开始就有这个字符串。为此,可以使用 `to_string` 方法,它能用于任何实现了 `Display` trait 的类型,比如字符串字面值。示例 8-12 展示了两个例子。 ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-12/src/main.rs:here}} @@ -44,7 +43,7 @@ 因为字符串应用广泛,这里有很多不同的用于字符串的通用 API 可供选择。其中一些可能看起来多余,不过都有其用武之地!在这个例子中,`String::from` 和 `.to_string` 最终做了完全相同的工作,所以如何选择就是代码风格与可读性的问题了。 -记住字符串是 UTF-8 编码的,所以可以包含任何可以正确编码的数据,如示例 8-14 所示。 +记住字符串是 UTF-8 编码的,所以可以包含任何经过正确编码的数据,如示例 8-14 所示。 ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-14/src/main.rs:here}} @@ -78,7 +77,7 @@ 如果 `push_str` 方法获取了 `s2` 的所有权,就不能在最后一行打印出其值了。好在代码如我们期望那样工作! -`push` 方法被定义为获取一个单独的字符作为参数,并附加到 `String` 中。示例 8-17 展示了使用 `push` 方法将字母 "l" 加入 `String` 的代码。 +`push` 方法被定义为获取一个单独的字符作为参数,并附加到 `String` 中。示例 8-17 展示了使用 `push` 方法将字母 *l* 加入 `String` 的代码。 ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-17/src/main.rs:here}} @@ -86,7 +85,7 @@ 示例 8-17:使用 `push` 将一个字符加入 `String` 值中 -执行这些代码之后,`s` 将会包含 “lol”。 +执行这些代码之后,`s` 将会包含 `lol`。 #### 使用 `+` 运算符或 `format!` 宏拼接字符串 @@ -106,25 +105,25 @@ fn add(self, s: &str) -> String { 在标准库中你会发现,`add` 的定义使用了泛型和关联类型。在这里我们替换为了具体类型,这也正是当使用 `String` 值调用这个方法会发生的。第十章会讨论泛型。这个签名提供了理解 `+` 运算那微妙部分的线索。 -首先,`s2` 使用了 `&`,意味着我们使用第二个字符串的 **引用** 与第一个字符串相加。这是因为 `add` 函数的 `s` 参数:只能将 `&str` 和 `String` 相加,不能将两个 `String` 值相加。不过等一下 —— `&s2` 的类型是 `&String`, 而不是 `add` 第二个参数所指定的 `&str`。那么为什么示例 8-18 还能编译呢? +首先,`s2` 使用了 `&`,意味着我们使用第二个字符串的**引用**与第一个字符串相加。这是因为 `add` 函数的 `s` 参数:只能将 `&str` 和 `String` 相加,不能将两个 `String` 值相加。不过等一下 —— `&s2` 的类型是 `&String`, 而不是 `add` 第二个参数所指定的 `&str`。那么为什么示例 8-18 还能编译呢? -之所以能够在 `add` 调用中使用 `&s2` 是因为 `&String` 可以被 **强转**(*coerced*)成 `&str`。当`add`函数被调用时,Rust 使用了一个被称为 **Deref 强制转换**(*deref coercion*)的技术,你可以将其理解为它把 `&s2` 变成了 `&s2[..]`。第十五章会更深入的讨论 Deref 强制转换。因为 `add` 没有获取参数的所有权,所以 `s2` 在这个操作后仍然是有效的 `String`。 +之所以能够在 `add` 调用中使用 `&s2` 是因为 `&String` 可以被 **强转**(*coerced*)成 `&str`。当`add`函数被调用时,Rust 使用了一个被称为 **Deref 强制转换**(*deref coercion*)的技术,实际上会把 `&s2` 转换为 `&s2[..]`。第十五章会更深入的讨论 Deref 强制转换。因为 `add` 没有获取参数的所有权,所以在这个操作后 `s2` 仍然是有效的 `String`。 -其次,可以发现签名中 `add` 获取了 `self` 的所有权,因为 `self` **没有** 使用 `&`。这意味着示例 8-18 中的 `s1` 的所有权将被移动到 `add` 调用中,之后就不再有效。所以虽然 `let s3 = s1 + &s2;` 看起来就像它会复制两个字符串并创建一个新的字符串,而实际上这个语句会获取 `s1` 的所有权,附加上从 `s2` 中拷贝的内容,并返回结果的所有权。换句话说,它看起来好像生成了很多拷贝,不过实际上并没有:这个实现比拷贝要更高效。 +其次,可以发现签名中 `add` 获取了 `self` 的所有权,因为 `self` **没有**使用 `&`。这意味着示例 8-18 中的 `s1` 的所有权将被移动到 `add` 调用中,之后就不再有效。所以虽然 `let s3 = s1 + &s2;` 看起来就像它会复制两个字符串并创建一个新的字符串,而实际上这个语句会获取 `s1` 的所有权,附加上从 `s2` 中拷贝的内容,并返回结果的所有权。换句话说,它看起来好像生成了很多拷贝,不过实际上并没有:这个实现比拷贝要更高效。 -如果想要级联多个字符串,`+` 的行为就显得笨重了: +如果想要级联多个字符串,`+` 运算符的行为就显得笨重了: ```rust {{#rustdoc_include ../listings/ch08-common-collections/no-listing-01-concat-multiple-strings/src/main.rs:here}} ``` -这时 `s` 的内容会是 “tic-tac-toe”。在有这么多 `+` 和 `"` 字符的情况下,很难理解具体发生了什么。对于更为复杂的字符串链接,可以使用 `format!` 宏: +这时 `s` 的内容会是 `tic-tac-toe`。在有这么多 `+` 和 `"` 字符的情况下,很难理解具体发生了什么。对于更为复杂的字符串链接,可以使用 `format!` 宏: ```rust {{#rustdoc_include ../listings/ch08-common-collections/no-listing-02-format/src/main.rs:here}} ``` -这些代码也会将 `s` 设置为 “tic-tac-toe”。`format!` 与 `println!` 的工作原理相同,不过不同于将输出打印到屏幕上,它返回一个带有结果内容的 `String`。这个版本就好理解的多,宏 `format!` 生成的代码使用引用所以不会获取任何参数的所有权。 +这些代码也会将 `s` 设置为 `tic-tac-toe`。`format!` 与 `println!` 的工作原理相同,不过不同于将输出打印到屏幕上,它返回一个带有结果内容的 `String`。这个版本就好理解的多,宏 `format!` 生成的代码使用引用因此不会获取任何参数的所有权。 ### 索引字符串 @@ -142,36 +141,36 @@ fn add(self, s: &str) -> String { {{#include ../listings/ch08-common-collections/listing-08-19/output.txt}} ``` -错误和提示说明了全部问题:Rust 的字符串不支持索引。那么接下来的问题是,为什么不支持呢?为了回答这个问题,我们必须先聊一聊 Rust 是如何在内存中储存字符串的。 +错误和提示说明了全部问题:Rust 的字符串不支持索引。那么,为什么会这样呢?为了回答这个问题,我们必须先聊一聊 Rust 是如何在内存中储存字符串的。 #### 内部表现 -`String` 是一个 `Vec` 的封装。让我们看看示例 8-14 中一些正确编码的字符串的例子。首先是这一个: +`String` 是一个 `Vec` 的封装。让我们看看示例 8-14 中一些正确编码的字符串的例子。首先是这一例: ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-14/src/main.rs:spanish}} ``` -在这里,`len` 的值是 4,这意味着储存字符串 “Hola” 的 `Vec` 的长度是四个字节:这里每一个字母的 UTF-8 编码都占用一个字节。那下面这个例子又如何呢?(注意这个字符串中的首字母是西里尔字母的 Ze 而不是数字 3。) +在这里,`len` 的值是 `4`,这意味着储存字符串 `"Hola"` 的 vector 的长度是四个字节:这里每一个字母的 UTF-8 编码都占用一个字节。下面这一行可能会让你感到意外(注意这个字符串中的首字母是西里尔字母的 *Ze* 而不是数字 3。): ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-14/src/main.rs:russian}} ``` -当问及这个字符是多长的时候有人可能会说是 12。然而,Rust 的回答是 24。这是使用 UTF-8 编码 “Здравствуйте” 所需要的字节数,这是因为每个 Unicode 标量值需要两个字节存储。因此一个字符串字节值的索引并不总是对应一个有效的 Unicode 标量值。作为演示,考虑如下无效的 Rust 代码: +如果有人问及该字符串的长度,你可能会回答 12。然而,Rust 的回答是 24:这是使用 UTF-8 编码 “Здравствуйте” 所需要的字节数,这是因为在这个字符串中每个 Unicode 标量值需要两个字节存储。因此一个字符串字节值的索引并不总是对应一个有效的 Unicode 标量值。作为演示,考虑如下无效的 Rust 代码: ```rust,ignore,does_not_compile let hello = "Здравствуйте"; let answer = &hello[0]; ``` -我们已经知道 `answer` 不是第一个字符 `3`。当使用 UTF-8 编码时,(西里尔字母的 Ze)`З` 的第一个字节是 `208`,第二个是 `151`,所以 `answer` 实际上应该是 `208`,不过 `208` 自身并不是一个有效的字母。返回 `208` 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回。即使这个字符串只有拉丁字母,如果 `&"hello"[0]` 是返回字节值的有效代码,它也会返回 `104` 而不是 `h`。 +我们已经知道 `answer` 不是第一个字符 `З`。当使用 UTF-8 编码时,`З` 的第一个字节是 `208`,第二个是 `151`,所以 `answer` 实际上应该是 `208`,不过 `208` 自身并不是一个有效的字母。返回 `208` 可不是一个请求字符串第一个字母的人所希望看到的,不过它是 Rust 在字节索引 0 位置所能提供的唯一数据。用户通常不会想要一个字节值被返回,即使这个字符串只有拉丁字母,如果 `&"hi"[0]` 是返回字节值的有效代码,它也会返回 `104` 而不是 `h`。 为了避免返回意外的值并造成不能立刻发现的 bug,Rust 根本不会编译这些代码,并在开发过程中及早杜绝了误会的发生。 #### 字节、标量值和字形簇!天呐! -这引起了关于 UTF-8 的另外一个问题:从 Rust 的角度来讲,事实上有三种相关方式可以理解字符串:字节、标量值和字形簇(最接近人们眼中 **字母** 的概念)。 +这引起了关于 UTF-8 的另外一个问题:从 Rust 的角度来讲,事实上有三种相关方式可以查看字符串:字节、标量值和字形簇(最接近人们眼中 **字母**(*letters*)的概念)。 比如这个用梵文书写的印度语单词 “नमस्ते”,最终它储存在 vector 中的 `u8` 值看起来像这样: @@ -206,15 +205,15 @@ let hello = "Здравствуйте"; let s = &hello[0..4]; ``` -这里,`s` 会是一个 `&str`,它包含字符串的头四个字节。早些时候,我们提到了这些字母都是两个字节长的,所以这意味着 `s` 将会是 “Зд”。 +这里,`s` 会是一个 `&str`,它包含字符串的头四个字节。早些时候,我们提到了这些字母都是两个字节长的,所以这意味着 `s` 将会是 `Зд`。 -如果获取 `&hello[0..1]` 会发生什么呢?答案是:Rust 在运行时会 panic,就跟访问 vector 中的无效索引时一样: +如果尝试用类似 `&hello[0..1]` 的方式对字符的部分字节进行 slice,Rust 会在运行时 panic,就跟访问 vector 中的无效索引时一样: ```console {{#include ../listings/ch08-common-collections/output-only-01-not-char-boundary/output.txt}} ``` -你应该小心谨慎地使用这个操作,因为这么做可能会使你的程序崩溃。 +在使用 range 来创建字符串 slice 时要格外小心,因为这么做可能会使你的程序崩溃。 ### 遍历字符串的方法 @@ -241,7 +240,7 @@ for b in "Зд".bytes() { } ``` -这些代码会打印出组成 `String` 的 4 个字节: +这些代码会打印出组成字符串的四个字节: ```text 208 @@ -250,17 +249,14 @@ for b in "Зд".bytes() { 180 ``` -不过请记住有效的 Unicode 标量值可能会由不止一个字节组成。 +不过请务必记住有效的 Unicode 标量值可能会由不止一个字节组成。 从字符串中获取如同天城文这样的字形簇是很复杂的,所以标准库并没有提供这个功能。[crates.io](https://crates.io/) 上有些提供这样功能的 crate。 ### 字符串并不简单 -总而言之,字符串还是很复杂的。不同的语言选择了不同的向程序员展示其复杂性的方式。Rust 选择了以准确的方式处理 `String` 数据作为所有 Rust 程序的默认行为,这意味着程序员们必须更多的思考如何预先处理 UTF-8 数据。这种权衡取舍相比其他语言更多的暴露出了字符串的复杂性,不过也使你在开发周期后期免于处理涉及非 ASCII 字符的错误。 +总而言之,字符串还是很复杂的。不同的语言选择了不同的向程序员展示其复杂性的方式。Rust 选择了以准确的方式处理 `String` 数据作为所有 Rust 程序的默认行为,这意味着程序员们必须更多的思考如何预先处理 UTF-8 数据。这种权衡相比其他语言更多地暴露出了字符串的复杂性,不过也使你在开发周期后期免于处理涉及非 ASCII 字符的错误。 好消息是标准库提供了很多围绕 `String` 和 `&str` 构建的功能,来帮助我们正确处理这些复杂场景。请务必查看这些使用方法的文档,例如 `contains` 来搜索一个字符串,和 `replace` 将字符串的一部分替换为另一个字符串。 - -称作 `String` 的类型是由标准库提供的,而没有写进核心语言部分,它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。当 Rustacean 们谈到 Rust 的 “字符串”时,它们通常指的是 `String` 或字符串 slice `&str` 类型,而不特指其中某一个。虽然本部分内容大多是关于 `String` 的,不过这两个类型在 Rust 标准库中都被广泛使用,`String` 和字符串 slices 都是 UTF-8 编码的。 - 现在让我们转向一些不太复杂的集合:哈希 map! diff --git a/src/ch08-03-hash-maps.md b/src/ch08-03-hash-maps.md index 8f2f901..bb15d4c 100644 --- a/src/ch08-03-hash-maps.md +++ b/src/ch08-03-hash-maps.md @@ -1,18 +1,17 @@ ## 使用 Hash Map 储存键值对 -> [ch08-03-hash-maps.md](https://github.com/rust-lang/book/blob/main/src/ch08-03-hash-maps.md) ->
-> commit 50775360ba3904c41e84176337ff47e6e7d6177c + + -最后介绍的常用集合类型是 **哈希 map**(*hash map*)。`HashMap` 类型储存了一个键类型 `K` 对应一个值类型 `V` 的映射。它通过一个 **哈希函数**(*hashing function*)来实现映射,决定如何将键和值放入内存中。很多编程语言支持这种数据结构,不过通常有不同的名字:哈希、map、对象、哈希表或者关联数组,仅举几例。 +最后介绍的常用集合类型是**哈希 map**(*hash map*)。`HashMap` 类型储存了一个键类型 `K` 对应一个值类型 `V` 的映射。它通过一个**哈希函数**(*hashing function*)来实现映射,决定如何将键和值放入内存中。很多编程语言支持这种数据结构,不过通常有不同的名字:**哈希**、**map**、**对象**、**哈希表**、**字典**或者**关联数组**,仅举几例。 -哈希 map 可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector 那样通过索引。例如,在一个游戏中,你可以将每个团队的分数记录到哈希 map 中,其中键是队伍的名字而值是每个队伍的分数。给出一个队名,就能得到他们的得分。 +哈希 map 可以用于需要任何类型作为键来寻找数据的情况,而不是像 vector 那样通过索引。例如,在一个游戏中,你可以将每个团队的分数记录到哈希 map 中,其中键是队伍的名字而值是每个队伍的分数。给出一个队名,就能检索到该队的得分。 本章我们会介绍哈希 map 的基本 API,不过还有更多吸引人的功能隐藏于标准库在 `HashMap` 上定义的函数中。一如既往请查看标准库文档来了解更多信息。 ### 新建一个哈希 map -可以使用 `new` 创建一个空的 `HashMap`,并使用 `insert` 增加元素。在示例 8-20 中我们记录两支队伍的分数,分别是蓝队和黄队。蓝队开始有 10 分而黄队开始有 50 分: +可以使用 `new` 创建一个空的 `HashMap`,并使用 `insert` 增加元素。在示例 8-20 中我们记录两支队伍的分数,分别是**蓝队**和**黄队**。蓝队开始有 10 分而黄队开始有 50 分: ```rust {{#rustdoc_include ../listings/ch08-common-collections/listing-08-20/src/main.rs:here}}