update ch08 close #679

pull/680/head
KaiserY 2 years ago
parent b58a6ce83a
commit 7c7049ed34

@ -2,7 +2,7 @@
> [ch08-01-vectors.md](https://github.com/rust-lang/book/blob/main/src/ch08-01-vectors.md)
> <br>
> commit e7bfb353b107cb150faab9d331c99ea2b91f3725
> commit ac16184a7f56d17daa9c4c76901371085dc0ac43
我们要讲到的第一个类型是 `Vec<T>`,也被称为 _vector_。vector 允许我们在一个单独的数据结构中储存多于一个的值它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用,例如文件中的文本行或是购物车中商品的价格。
@ -40,99 +40,106 @@
如第三章中讨论的任何变量一样,如果想要能够改变它的值,必须使用 `mut` 关键字使其可变。放入其中的所有值都是 `i32` 类型的,而且 Rust 也根据数据做出如此判断,所以不需要 `Vec<i32>` 注解。
### 丢弃 vector 时也会丢弃其所有元素
类似于任何其他的 `struct`vector 在其离开作用域时会被释放,如示例 8-4 所标注的:
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-04/src/main.rs:here}}
```
<span class="caption">示例 8-4展示 vector 和其元素于何处被丢弃</span>
当 vector 被丢弃时,所有其内容也会被丢弃,这意味着这里它包含的整数将被清理。这可能看起来非常直观,不过一旦开始使用 vector 元素的引用,情况就变得有些复杂了。下面让我们处理这种情况!
### 读取 vector 的元素
现在你知道如何创建、更新和销毁 vector 了,接下来的一步最好了解一下如何读取它们的内容。有两种方法引用 vector 中储存的值。为了更加清楚的说明这个例子,我们标注这些函数返回的值的类型。
示例 8-5 展示了访问 vector 中一个值的两种方式,索引语法或者 `get` 方法:
示例 8-4 展示了访问 vector 中一个值的两种方式,索引语法或者 `get` 方法:
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-05/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-04/src/main.rs:here}}
```
<span class="caption">列表 8-5:使用索引语法或 `get` 方法来访问 vector 中的项</span>
<span class="caption">列表 8-4使用索引语法或 `get` 方法来访问 vector 中的项</span>
这里有两个需要注意的地方。首先,我们使用索引值 `2` 来获取第三个元素,索引是从 0 开始的。其次,这两个不同的获取第三个元素的方式分别为:使用 `&``[]` 返回一个引用;或者使用 `get` 方法以索引作为参数来返回一个 `Option<&T>`
这里有几个细节需要注意。我们使用索引值 `2` 来获取第三个元素,因为索引是从数字 0 开始的。使用 `&``[]` 会得到一个索引位置元素的引用。当使用索引作为参数调用 `get` 方法时,会得到一个可以用于 `match` `Option<&T>`
Rust 提供了两种引用元素的方法的原因是当尝试使用现有元素范围之外的索引值时可以选择让程序如何运行。举个例子,让我们看看使用这个技术,尝试在当有一个 5 个元素的 vector 接着访问索引 100 位置的元素会发生什么,如示例 8-6 所示:
Rust 提供了两种引用元素的方法的原因是当尝试使用现有元素范围之外的索引值时可以选择让程序如何运行。举个例子,让我们看看使用这个技术,尝试在当有一个 5 个元素的 vector 接着访问索引 100 位置的元素会发生什么,如示例 8-5 所示:
```rust,should_panic,panics
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-06/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-05/src/main.rs:here}}
```
<span class="caption">示例 8-6:尝试访问一个包含 5 个元素的 vector 的索引 100 处的元素</span>
<span class="caption">示例 8-5:尝试访问一个包含 5 个元素的 vector 的索引 100 处的元素</span>
当运行这段代码,你会发现对于第一个 `[]` 方法,当引用一个不存在的元素时 Rust 会造成 panic。这个方法更适合当程序认为尝试访问超过 vector 结尾的元素是一个严重错误的情况,这时应该使程序崩溃。
`get` 方法被传递了一个数组外的索引时,它不会 panic 而是返回 `None`。当偶尔出现超过 vector 范围的访问属于正常情况的时候可以考虑使用它。接着你的代码可以有处理 `Some(&element)``None` 的逻辑,如第六章讨论的那样。例如,索引可能来源于用户输入的数字。如果它们不慎输入了一个过大的数字那么程序就会得到 `None` 值,你可以告诉用户当前 vector 元素的数量并再请求它们输入一个有效的值。这就比因为输入错误而使程序崩溃要友好的多!
一旦程序获取了一个有效的引用,借用检查器将会执行所有权和借用规则(第四章讲到)来确保 vector 内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。这个规则适用于示例 8-7,当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候,如果尝试在函数的后面引用这个元素是行不通的:
一旦程序获取了一个有效的引用,借用检查器将会执行所有权和借用规则(第四章讲到)来确保 vector 内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。这个规则适用于示例 8-6,当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候,如果尝试在函数的后面引用这个元素是行不通的:
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-07/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-06/src/main.rs:here}}
```
<span class="caption">示例 8-7:在拥有 vector 中项的引用的同时向其增加一个元素</span>
<span class="caption">示例 8-6:在拥有 vector 中项的引用的同时向其增加一个元素</span>
编译会给出这个错误:
```console
{{#include ../listings/ch08-common-collections/listing-08-07/output.txt}}
{{#include ../listings/ch08-common-collections/listing-08-06/output.txt}}
```
示例 8-7 中的代码看起来应该能够运行:为什么第一个元素的引用会关心 vector 结尾的变化?不能这么做的原因是由于 vector 的工作方式:在 vector 的结尾增加新元素时,在没有足够空间将所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。
示例 8-6 中的代码看起来应该能够运行:为什么第一个元素的引用会关心 vector 结尾的变化?不能这么做的原因是由于 vector 的工作方式:在 vector 的结尾增加新元素时,在没有足够空间将所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状况。
> 注意:关于 `Vec<T>` 类型的更多实现细节,请查看 [“The Rustonomicon”][nomicon]
### 遍历 vector 中的元素
如果想要依次访问 vector 中的每一个元素,我们可以遍历其所有的元素而无需通过索引一次一个的访问。示例 8-8 展示了如何使用 `for` 循环来获取 `i32` 值的 vector 中的每一个元素的不可变引用并将其打印:
如果想要依次访问 vector 中的每一个元素,我们可以遍历其所有的元素而无需通过索引一次一个的访问。示例 8-7 展示了如何使用 `for` 循环来获取 `i32` 值的 vector 中的每一个元素的不可变引用并将其打印:
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-08/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-07/src/main.rs:here}}
```
<span class="caption">示例 8-8:通过 `for` 循环遍历 vector 的元素并打印</span>
<span class="caption">示例 8-7:通过 `for` 循环遍历 vector 的元素并打印</span>
我们也可以遍历可变 vector 的每一个元素的可变引用以便能改变他们。示例 8-9 中的 `for` 循环会给每一个元素加 `50`
我们也可以遍历可变 vector 的每一个元素的可变引用以便能改变他们。示例 8-8 中的 `for` 循环会给每一个元素加 `50`
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-09/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-08/src/main.rs:here}}
```
<span class="caption">示例 8-9:遍历 vector 中元素的可变引用</span>
<span class="caption">示例 8-8:遍历 vector 中元素的可变引用</span>
为了修改可变引用所指向的值,在使用 `+=` 运算符之前必须使用解引用运算符(`*`)获取 `i` 中的值。第十五章的 [“通过解引用运算符追踪指针的值”][deref] 部分会详细介绍解引用运算符。
因为借用检查器的规则,无论可变还是不可变地遍历一个 vector 都是安全。如果尝试在示例 8-7 和 示例 8-8 的 `for` 循环体内插入或删除项,都会得到一个类似示例 8-6 代码中类似的编译错误。`for` 循环中获取的 vector 引用阻止了同时对 vector 整体的修改。
### 使用枚举来储存多种类型
vector 只能储存相同类型的值。这是很不方便的;绝对会有需要储存一系列不同类型的值的用例。幸运的是,枚举的成员都被定义为相同的枚举类型,所以当需要在 vector 中储存不同类型值时,我们可以定义并使用一个枚举!
例如,假如我们想要从电子表格的一行中获取值,而这一行的有些列包含数字,有些包含浮点值,还有些是字符串。我们可以定义一个枚举,其成员会存放这些不同类型的值,同时所有这些枚举成员都会被当作相同类型,那个枚举的类型。接着可以创建一个储存枚举值的 vector这样最终就能够储存不同类型的值了。示例 8-10 展示了其用例:
例如,假如我们想要从电子表格的一行中获取值,而这一行的有些列包含数字,有些包含浮点值,还有些是字符串。我们可以定义一个枚举,其成员会存放这些不同类型的值,同时所有这些枚举成员都会被当作相同类型,那个枚举的类型。接着可以创建一个储存枚举值的 vector这样最终就能够储存不同类型的值了。示例 8-9 展示了其用例:
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-10/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-09/src/main.rs:here}}
```
<span class="caption">示例 8-10:定义一个枚举,以便能在 vector 中存放不同类型的数据</span>
<span class="caption">示例 8-9:定义一个枚举,以便能在 vector 中存放不同类型的数据</span>
Rust 在编译时就必须准确的知道 vector 中类型的原因在于它需要知道储存每个元素到底需要多少内存。第二个好处是可以准确的知道这个 vector 中允许什么类型。如果 Rust 允许 vector 存放任意类型,那么当对 vector 元素执行操作时一个或多个类型的值就有可能会造成错误。使用枚举外加 `match` 意味着 Rust 能在编译时就保证总是会处理所有可能的情况,正如第六章讲到的那样。
如果在编写程序时不能确切无遗地知道运行时会储存进 vector 的所有类型,枚举技术就行不通了。相反,你可以使用 trait 对象,第十七章会讲到它。
现在我们了解了一些使用 vector 的最常见的方式,请一定去看看标准库中 `Vec` 定义的很多其他实用方法的 [API 文档][vec-api]。例如,除了 `push` 之外还有一个 `pop` 方法,它会移除并返回 vector 的最后一个元素。让我们继续下一个集合类型:`String`
现在我们了解了一些使用 vector 的最常见的方式,请一定去看看标准库中 `Vec` 定义的很多其他实用方法的 [API 文档][vec-api]。例如,除了 `push` 之外还有一个 `pop` 方法,它会移除并返回 vector 的最后一个元素。
### 丢弃 vector 时也会丢弃其所有元素
类似于任何其他的 `struct`vector 在其离开作用域时会被释放,如示例 8-4 所标注的:
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-10/src/main.rs:here}}
```
<span class="caption">示例 8-10展示 vector 和其元素于何处被丢弃</span>
当 vector 被丢弃时,所有其内容也会被丢弃,这意味着这里它包含的整数将被清理。借用检查器确保了任何 vector 中内容的引用仅在 vector 本身有效时才可用。
这可能看起来非常直观,不过一旦开始使用 vector 元素的引用,情况就变得有些复杂了。下面让我们处理这种情况!
让我们继续下一个集合类型:`String`
[data-types]: ch03-02-data-types.html#数据类型
[nomicon]: https://doc.rust-lang.org/nomicon/vec/vec.html

@ -2,7 +2,7 @@
> [ch08-02-strings.md](https://github.com/rust-lang/book/blob/main/src/ch08-02-strings.md)
> <br>
> commit db403a8bdfe5223d952737f54b0d9651b3e6ae1d
> commit 06ea00d9cadd072b6a853c987b34da2633cddbb8
第四章已经讲过一些字符串的内容,不过现在让我们更深入地了解它。字符串是新晋 Rustacean 们通常会被困住的领域这是由于三方面理由的结合Rust 倾向于确保暴露出可能的错误,字符串是比很多程序员所想象的要更为复杂的数据结构,以及 UTF-8。所有这些要素结合起来对于来自其他语言背景的程序员就可能显得很困难了。
@ -12,11 +12,9 @@
在开始深入这些方面之前,我们需要讨论一下术语 **字符串** 的具体意义。Rust 的核心语言中只有一种字符串类型:字符串 slice `str`,它通常以被借用的形式出现,`&str`。第四章讲到了 **字符串 slices**:它们是一些对储存在别处的 UTF-8 编码字符串数据的引用。举例来说,由于字符串字面值被储存在程序的二进制输出中,因此字符串字面值也是字符串 slices。
称作 `String` 的类型是由标准库提供的而没有写进核心语言部分它是可增长的、可变的、有所有权的、UTF-8 编码的字符串类型。当 Rustacean 们谈到 Rust 的 “字符串”时,它们通常指的是 `String` 或字符串 slice `&str` 类型,而不特指其中某一个。虽然本部分内容大多是关于 `String` 的,不过这两个类型在 Rust 标准库中都被广泛使用,`String` 和字符串 slices 都是 UTF-8 编码的。
### 新建字符串
很多 `Vec` 可用的操作在 `String` 中同样可用,从以 `new` 函数创建字符串开始,如示例 8-11 所示。
很多 `Vec` 可用的操作在 `String` 中同样可用,事实上 `String` 被实现为一个带有一些额外保证、限制和功能的字节 vector 的封装。其中一个同样作用于 `Vec<T>``String` 函数的例子是用来新建一个实例的 `new` 函数,如示例 8-11 所示。
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-11/src/main.rs:here}}
@ -104,7 +102,7 @@
fn add(self, s: &str) -> String {
```
这并不是标准库中实际的签名;标准库中的 `add` 使用泛型定义。这里我们看到的 `add` 的签名使用具体类型代替了泛型,这也正是当使用 `String` 值调用这个方法会发生的。第十章会讨论泛型。这个签名提供了理解 `+` 运算那微妙部分的线索。
在标准库中你会发现,`add` 的定义使用了泛型和关联类型。在这里我们替换为了具体类型,这也正是当使用 `String` 值调用这个方法会发生的。第十章会讨论泛型。这个签名提供了理解 `+` 运算那微妙部分的线索。
首先,`s2` 使用了 `&`,意味着我们使用第二个字符串的 **引用** 与第一个字符串相加。这是因为 `add` 函数的 `s` 参数:只能将 `&str``String` 相加,不能将两个 `String` 值相加。不过等一下 —— 正如 `add` 的第二个参数所指定的,`&s2` 的类型是 `&String` 而不是 `&str`。那么为什么示例 8-18 还能编译呢?
@ -218,49 +216,49 @@ let s = &hello[0..4];
### 遍历字符串的方法
操作字符串每一部分的最好的方法是明确表示需要字符还是字节。对于单独的 Unicode 标量值使用 `chars` 方法。对 “नमस्ते” 调用 `chars` 方法会将其分开并返回六`char` 类型的值,接着就可以遍历其结果来访问每一个元素了:
操作字符串每一部分的最好的方法是明确表示需要字符还是字节。对于单独的 Unicode 标量值使用 `chars` 方法。对 “Зд” 调用 `chars` 方法会将其分开并返回两`char` 类型的值,接着就可以遍历其结果来访问每一个元素了:
```rust
for c in "नमस्ते".chars() {
println!("{}", c);
for c in "Зд".chars() {
println!("{c}");
}
```
这些代码会打印出如下内容:
```text
З
д
```
另外 `bytes` 方法返回每一个原始字节,这可能会适合你的使用场景:
```rust
for b in "नमस्ते".bytes() {
println!("{}", b);
for b in "Зд".bytes() {
println!("{b}");
}
```
这些代码会打印出组成 `String`18 个字节:
这些代码会打印出组成 `String`4 个字节:
```text
224
164
// --snip--
165
135
208
151
208
180
```
不过请记住有效的 Unicode 标量值可能会由不止一个字节组成。
从字符串中获取字形簇是很复杂的,所以标准库并没有提供这个功能。[crates.io](https://crates.io/)<!-- ignore --> 上有些提供这样功能的 crate。
从字符串中获取如同天城文这样的字形簇是很复杂的,所以标准库并没有提供这个功能。[crates.io](https://crates.io/)<!-- ignore --> 上有些提供这样功能的 crate。
### 字符串并不简单
总而言之字符串还是很复杂的。不同的语言选择了不同的向程序员展示其复杂性的方式。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

@ -2,7 +2,7 @@
> [ch08-03-hash-maps.md](https://github.com/rust-lang/book/blob/main/src/ch08-03-hash-maps.md)
> <br>
> commit 1fd890031311612e54965f7f800a8c8bd4464663
> commit 50775360ba3904c41e84176337ff47e6e7d6177c
最后介绍的常用集合类型是 **哈希 map***hash map*)。`HashMap<K, V>` 类型储存了一个键类型 `K` 对应一个值类型 `V` 的映射。它通过一个 **哈希函数***hashing function*来实现映射决定如何将键和值放入内存中。很多编程语言支持这种数据结构不过通常有不同的名字哈希、map、对象、哈希表或者关联数组仅举几例。
@ -24,41 +24,17 @@
像 vector 一样,哈希 map 将它们的数据储存在堆上,这个 `HashMap` 的键类型是 `String` 而值类型是 `i32`。类似于 vector哈希 map 是同质的:所有的键必须是相同类型,值也必须都是相同类型。
另一个构建哈希 map 的方法是在一个元组的 vector 上使用迭代器iterator`collect` 方法,其中每个元组包含一个键值对。我们会在[第十三章的 “Processing a Series of Items with Iterators” 部分][iterators]<!-- ignore --> 介绍迭代器及其关联方法。`collect` 方法可以将数据收集进一系列的集合类型,包括 `HashMap`。例如,如果队伍的名字和初始分数分别在两个 vector 中,可以使用 `zip` 方法来创建一个元组的迭代器,其中 “Blue” 与 10 是一对,依此类推。接着就可以使用 `collect` 方法将这个元组的迭代器转换成一个 `HashMap`,如示例 8-21 所示:
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-21/src/main.rs:here}}
```
<span class="caption">示例 8-21用队伍列表和分数列表创建哈希 map</span>
这里 `HashMap<_, _>` 类型注解是必要的,因为可能 `collect` 为很多不同的数据结构,而除非显式指定否则 Rust 无从得知你需要的类型。但是对于键和值的类型参数来说,可以使用下划线占位,而 Rust 能够根据 vector 中数据的类型推断出 `HashMap` 所包含的类型。在示例 8-21 中key类型是 `String`value类型是 `i32`,与示例 8-20 的类型一样。
### 哈希 map 和所有权
对于像 `i32` 这样的实现了 `Copy` trait 的类型,其值可以拷贝进哈希 map。对于像 `String` 这样拥有所有权的值,其值将被移动而哈希 map 会成为这些值的所有者,如示例 8-22 所示:
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-22/src/main.rs:here}}
```
<span class="caption">示例 8-22展示一旦键值对被插入后就为哈希 map 所拥有</span>
`insert` 调用将 `field_name``field_value` 移动到哈希 map 中后,将不能使用这两个绑定。
如果将值的引用插入哈希 map这些值本身将不会被移动进哈希 map。但是这些引用指向的值必须至少在哈希 map 有效时也是有效的。第十章 [“生命周期与引用有效性”][validating-references-with-lifetimes] 部分将会更多的讨论这个问题。
### 访问哈希 map 中的值
可以通过 `get` 方法并提供对应的键来从哈希 map 中获取值,如示例 8-23 所示:
可以通过 `get` 方法并提供对应的键来从哈希 map 中获取值,如示例 8-21 所示:
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-23/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-21/src/main.rs:here}}
```
<span class="caption">示例 8-23:访问哈希 map 中储存的蓝队分数</span>
<span class="caption">示例 8-21访问哈希 map 中储存的蓝队分数</span>
这里,`score` 是与蓝队分数相关的值,应为 `Some(10)`。因为 `get` 返回 `Option<V>`所以结果被装进 `Some`如果某个键在哈希 map 中没有对应的值,`get` 会返回 `None`这时就要用某种第六章提到的方法之一来处理 `Option`
这里,`score` 是与蓝队分数相关的值,应为 `10`。`get` 方法返回 `Option<&V>`,如果某个键在哈希 map 中没有对应的值,`get` 会返回 `None`。程序中通过调用 `copied` 方法来获取一个 `Option<i32>` 而不是 `Option<&i32>`,接着调用 `unwrap_or`,如果 `score` 没有对应键的项将 `score` 设置为零。
可以使用与 vector 类似的方式来遍历哈希 map 中的每一个键值对,也就是 `for` 循环:
@ -73,47 +49,67 @@ Yellow: 50
Blue: 10
```
### 哈希 map 和所有权
对于像 `i32` 这样的实现了 `Copy` trait 的类型,其值可以拷贝进哈希 map。对于像 `String` 这样拥有所有权的值,其值将被移动而哈希 map 会成为这些值的所有者,如示例 8-22 所示:
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-22/src/main.rs:here}}
```
<span class="caption">示例 8-22展示一旦键值对被插入后就为哈希 map 所拥有</span>
`insert` 调用将 `field_name``field_value` 移动到哈希 map 中后,将不能使用这两个绑定。
如果将值的引用插入哈希 map这些值本身将不会被移动进哈希 map。但是这些引用指向的值必须至少在哈希 map 有效时也是有效的。第十章 [“生命周期与引用有效性”][validating-references-with-lifetimes] 部分将会更多的讨论这个问题。
### 更新哈希 map
尽管键值对的数量是可以增长的,不过任何时候,每个键只能关联一个值。当我们想要改变哈希 map 中的数据时,必须决定如何处理一个键已经有值了的情况。可以选择完全无视旧值并用新值代替旧值。可以选择保留旧值而忽略新值,并只在键 **没有** 对应值时增加新值。或者可以结合新旧两值。让我们看看这分别该如何处理!
尽管键值对的数量是可以增长的,每个唯一的键只能同时关联一个值(反之不一定成立:比如蓝队和黄队的 `scores` 哈希 map 中都可能存储有 10 这个值)。
当我们想要改变哈希 map 中的数据时,必须决定如何处理一个键已经有值了的情况。可以选择完全无视旧值并用新值代替旧值。可以选择保留旧值而忽略新值,并只在键 **没有** 对应值时增加新值。或者可以结合新旧两值。让我们看看这分别该如何处理!
#### 覆盖一个值
如果我们插入了一个键值对,接着用相同的键插入一个不同的值,与这个键相关联的旧值将被替换。即便示例 8-24 中的代码调用了两次 `insert`,哈希 map 也只会包含一个键值对,因为两次都是对蓝队的键插入的值:
如果我们插入了一个键值对,接着用相同的键插入一个不同的值,与这个键相关联的旧值将被替换。即便示例 8-23 中的代码调用了两次 `insert`,哈希 map 也只会包含一个键值对,因为两次都是对蓝队的键插入的值:
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-24/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-23/src/main.rs:here}}
```
<span class="caption">示例 8-24替换以特定键储存的值</span>
<span class="caption">示例 8-23:替换以特定键储存的值</span>
这会打印出 `{"Blue": 25}`。原始的值 `10` 则被覆盖了。
#### 只在键没有对应值时插入
#### 只在键没有对应值时插入键值对
我们经常会检查某个特定的键是否有值,如果没有就插入一个值。为此哈希 map 有一个特有的 API叫做 `entry`,它获取我们想要检查的键作为参数。`entry` 函数的返回值是一个枚举,`Entry`,它代表了可能存在也可能不存在的值。比如说我们想要检查黄队的键是否关联了一个值。如果没有,就插入值 50对于蓝队也是如此。使用 entry API 的代码看起来像示例 8-25 这样:
我们经常会检查某个特定的键是否已经存在于哈希 map 中并进行如下操作:如果哈希 map 中键已经存在则不做任何操作。如果不存在则连同值一块插入。
为此哈希 map 有一个特有的 API叫做 `entry`,它获取我们想要检查的键作为参数。`entry` 函数的返回值是一个枚举,`Entry`,它代表了可能存在也可能不存在的值。比如说我们想要检查黄队的键是否关联了一个值。如果没有,就插入值 50对于蓝队也是如此。使用 `entry` API 的代码看起来像示例 8-24 这样:
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-25/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-24/src/main.rs:here}}
```
<span class="caption">示例 8-25:使用 `entry` 方法只在键没有对应一个值时插入</span>
<span class="caption">示例 8-24:使用 `entry` 方法只在键没有对应一个值时插入</span>
`Entry``or_insert` 方法在键对应的值存在时就返回这个值的可变引用,如果不存在则将参数作为新值插入并返回新值的可变引用。这比编写自己的逻辑要简明的多,另外也与借用检查器结合得更好。
运行示例 8-25 的代码会打印出 `{"Yellow": 50, "Blue": 10}`。第一个 `entry` 调用会插入黄队的键和值 `50`,因为黄队并没有一个值。第二个 `entry` 调用不会改变哈希 map 因为蓝队已经有了值 `10`
运行示例 8-24 的代码会打印出 `{"Yellow": 50, "Blue": 10}`。第一个 `entry` 调用会插入黄队的键和值 `50`,因为黄队并没有一个值。第二个 `entry` 调用不会改变哈希 map 因为蓝队已经有了值 `10`
#### 根据旧值更新一个值
另一个常见的哈希 map 的应用场景是找到一个键对应的值并根据旧的值更新它。例如,示例 8-26 中的代码计数一些文本中每一个单词分别出现了多少次。我们使用哈希 map 以单词作为键并递增其值来记录我们遇到过几次这个单词。如果是第一次看到某个单词,就插入值 `0`
另一个常见的哈希 map 的应用场景是找到一个键对应的值并根据旧的值更新它。例如,示例 8-25 中的代码计数一些文本中每一个单词分别出现了多少次。我们使用哈希 map 以单词作为键并递增其值来记录我们遇到过几次这个单词。如果是第一次看到某个单词,就插入值 `0`
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-26/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-25/src/main.rs:here}}
```
<span class="caption">示例 8-26通过哈希 map 储存单词和计数来统计出现次数</span>
<span class="caption">示例 8-25通过哈希 map 储存单词和计数来统计出现次数</span>
这会打印出 `{"world": 2, "hello": 1, "wonderful": 1}`。你可能会发现相同的键值对以不同的顺序打印:回忆以下[“访问哈希 map 中的值”][access]部分中遍历哈希 map 会以任意顺序进行。
这会打印出 `{"world": 2, "hello": 1, "wonderful": 1}`。`split_whitespace` 方法会迭代 `text` 的值由空格分隔的子 slice。`or_insert` 方法返回这个键的值的一个可变引用(`&mut V`)。这里我们将这个可变引用储存在 `count` 变量中,所以为了赋值必须首先使用星号(`*`)解引用 `count`。这个可变引用在 `for` 循环的结尾离开作用域,这样所有这些改变都是安全的并符合借用规则。
`split_whitespace` 方法返回一个由空格分隔 `text` 值子 slice 的迭代器。`or_insert` 方法返回这个键的值的一个可变引用(`&mut V`)。这里我们将这个可变引用储存在 `count` 变量中,所以为了赋值必须首先使用星号(`*`)解引用 `count`。这个可变引用在 `for` 循环的结尾离开作用域,这样所有这些改变都是安全的并符合借用规则。
### 哈希函数
@ -136,3 +132,4 @@ vector、字符串和哈希 map 会在你的程序需要储存、访问和修改
[iterators]: ch13-02-iterators.html
[validating-references-with-lifetimes]:
ch10-03-lifetime-syntax.html#生命周期与引用有效性
[access]: #访问哈希-map-中的值

Loading…
Cancel
Save