@ -1,13 +1,12 @@
## 使用 Vector 储存列表
## 使用 Vector 储存列表
<!-- https://github.com/rust - lang/book/blob/main/src/ch08 - 01 - vectors.md -->
[ch08-01-vectors.md ](https://github.com/rust-lang/book/blob/2581c23b669eff30c26e036a13475ec5cf70c1b8/src/ch08-01-vectors.md )
<!-- commit 5d22a358fb2380aa3f270d7b6269b67b8e44849e -->
我们要讲到的第一个类型是 `Vec<T>` ,也被称为 *vector* 。vector 允许我们在一个单独的数据结构中储存多于一个的值, 它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。它们在拥有一系列项的场景下非常实用,例如文件中的文本行或是 购物车中商品的价格。
我们要讨论的第一种集合类型是 `Vec<T>` ,也被称为 *vector* 。vector 允许你在单个数据结构中存放多个值, 并把这些值在内存中彼此相邻地排列起来。vector 只能存储相同类型的值。当你有一组项目要处理时,它就很有用,例如文件中的文本行,或者 购物车中商品的价格。
### 新建 vector
### 新建 vector
为了新建一个空的 vector, 可以调用 `Vec::new` 函数,如示例 8-1 所示。
要创建一个新的空 vector, 可以调用 `Vec::new` 函数,如示例 8-1 所示。
```rust
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-01/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-01/src/main.rs:here}}
@ -15,9 +14,9 @@
< span class = "caption" > 示例 8-1: 新建一个空的 vector 来储存 `i32` 类型的值</ span >
< span class = "caption" > 示例 8-1: 新建一个空的 vector 来储存 `i32` 类型的值</ span >
注意这里我们增加了一个类型注解。因为没有向这个 vector 中插入任何值, Rust 并不知道我们想要储存什么类型的元素。这是一个非常重要的点。vector 是用泛型实现的,第十章会涉及到如何对你自己的类型使用它们。现在,所有你需要知道的就是 `Vec<T>` 是一个由标准库提供的类型,它可以存放任何类型,而当 `Vec` 存放某个特定类型时,那个类型位于尖括号中。在示例 8-1 中,我们告诉 Rust `v` 这个 `Vec<T>` 将存放 `i32` 类型的元素。
注意,这里我们加了一个类型注解。因为还没有往这个 vector 里插入任何值, Rust 并不知道我们打算存储什么类型的元素。这一点很重要。vector 是使用泛型实现的;第十章会讲到如何在你自己的类型上使用泛型。现在你只需要知道,标准库提供的 `Vec<T>` 类型可以容纳任意类型。当我们创建一个用来存放特定类型的 vector 时,可以在尖括号中指定这个类型。在示例 8-1 中,我们告诉 Rust, `v` 中的 `Vec<T>` 将存放 `i32` 类型的元素。
通常,我们会用初始值来创建一个 `Vec<T>` 而 Rust 会推断出储存值的类型,所以很少会需要这些类型注解。为了方便 Rust 提供了 `vec!` 宏,这个宏会根据我们提供的值来创建一个新的 vector。示例 8-2 新建一个拥有值 `1` 、`2` 和 `3` 的 `Vec<i32>` 。推断为 `i32` 是因为这是默认整型类型,第三章的 [“数据类型”][data-types] 讨论过 :
更常见的情况是,我们会用初始值创建 `Vec<T>` ,而 Rust 会推断出你想存储的值的类型, 所以很少需要写这种类型注解。Rust 还很贴心地提供了 `vec!` 宏,它会创建一个新的 vector, 并把你提供的值放进去。示例 8-2 创建了一个包含 `1` 、`2` 和 `3` 的新 `Vec<i32>` 。这里的整数类型之所以是 `i32` ,是因为它是默认整数类型,正如我们在第三章的[“数据类型”][data-types]部分讨论过的那样 :
```rust
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-02/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-02/src/main.rs:here}}
@ -25,11 +24,11 @@
< span class = "caption" > 示例 8-2: 新建一个包含初值的 vector< / span >
< span class = "caption" > 示例 8-2: 新建一个包含初值的 vector< / span >
因为我们提供 了 `i32` 类型的初始值, Rust 可以推断出 `v` 的类型是 `Vec<i32>` ,因此类型注解就不是必须的 。接下来让我们 看看如何修改一个 vector。
因为我们给出 了 `i32` 类型的初始值, Rust 可以推断出 `v` 的类型是 `Vec<i32>` ,因此这里不需要 类型注解。接下来看看如何修改 vector。
### 更新 vector
### 更新 vector
对于新建一个 vector 并向其增 加元素,可以使用 `push` 方法,如示例 8-3 所示:
要先创建一个 vector 再向其中添 加元素,可以使用 `push` 方法,如示例 8-3 所示:
```rust
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-03/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-03/src/main.rs:here}}
@ -37,13 +36,13 @@
< span class = "caption" > 示例 8-3: 使用 `push` 方法向 vector 增加值</ span >
< span class = "caption" > 示例 8-3: 使用 `push` 方法向 vector 增加值</ span >
如第三章中讨论的任何变量一样,如果想要能够改变它的值,必须使用 `mut` 关键字使其可变。放入其中的所有值都是 `i32` 类型的,而且 Rust 也根据数据做出如此判断,所以不需要 `Vec<i32>` 注解。
和任何变量一样,如果想修改它的值,就必须像第三章讲过的那样,使用 `mut` 关键字让它变成可变的。放进去的数字都是 `i32` 类型, Rust 会从数据中推断出这一点,因此也不需要写 `Vec<i32>` 注解。
### 读取 vector 的元素
### 读取 vector 的元素
有两种方法引用 vector 中储存的值:通过索引或使用 `get` 方法。在接下来的示例中,为了更加清楚的说明,我们已经标注了这些函数返回的值的 类型。
有两种方式可以引用 vector 中存储的值:通过索引,或者使用 `get` 方法。在接下来的示例中,为了更清楚地说明这一点,我们给这些函数返回的值标注了 类型。
示例 8-4 展示了访问 vector 中一个值的两种方式,索引语法或者 `get` 方法:
示例 8-4 展示了访问 vector 中某个值的两种方式:索引语法和 `get` 方法。
```rust
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-04/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-04/src/main.rs:here}}
@ -51,9 +50,9 @@
< span class = "caption" > 示例 8-4: 使用索引语法或 `get` 方法来访问 vector 中的项</ span >
< span class = "caption" > 示例 8-4: 使用索引语法或 `get` 方法来访问 vector 中的项</ span >
这里有几个细节需要注意。我们使 用索引值 `2` 来 获取第三个元素,因为索引是从数字 0 开始的。使用 `&` 和 `[]` 会得到一个索引位置元素的引用。当使用索引作为参数调用 `get` 方法时,会得到一个可以用于 `match` 的 `Option<&T>` 。
这里有几个细节需要注意。我们用索引值 `2` 获取第三个元素,因为 vector 的 索引是从 0 开始的。使用 `&` 和 `[]` 会得到索引位置处元素的引用。当我们把索引作为参数传给 `get` 方法时,会得到一个可以与 `match` 一起使用 的 `Option<&T>` 。
Rust 提供了两种引用元素的方法的原因是当尝试使用现有元素范围之外的索引值时可以选择让程序如何运行。举个例子,让我们看看使用这个技术,尝试在当有一个 5 个元素的 vector 接着访问索引 100 位置的元素 会发生什么,如示例 8-5 所示:
Rust 提供这两种引用元素的方式,是为了让你可以选择:当尝试使用超出已有元素范围的索引值时,程序该如何表现。举个例子,假设我们有一个包含 5 个元素的 vector, 然后尝试分别用这两种技术访问索引 100 处的元素,看看 会发生什么,如示例 8-5 所示:
```rust,should_panic,panics
```rust,should_panic,panics
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-05/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-05/src/main.rs:here}}
@ -61,11 +60,11 @@ Rust 提供了两种引用元素的方法的原因是当尝试使用现有元素
< span class = "caption" > 示例 8-5: 尝试访问一个包含 5 个元素的 vector 的索引 100 处的元素< / span >
< span class = "caption" > 示例 8-5: 尝试访问一个包含 5 个元素的 vector 的索引 100 处的元素< / span >
当运行这段代码,你会发现对于第一个 `[]` 方法,当引用一个不存在的元素时 Rust 会造成 panic。此方法适用于当你希望在尝试访问 vector 末尾之外的元素时让程序直接崩溃的场景 。
运行这段代码时,第一种 `[]` 方法会让程序 panic, 因为它引用了一个不存在的元素。当你希望程序在有人尝试访问 vector 末尾之外的元素时直接崩溃,这种方式就很合适 。
当 `get` 方法被传递了一个数组外的索引时,它不会 panic 而是返回 `None` 。当偶尔出现超过 vector 范围的访问属于正常情况的时候可以考虑使用它。接着你的代码可以有处理 `Some(&element)` 或 `None` 的逻辑,如第六章讨论的那样。例如,索引可能来源于用户输入的数字。如果它们不慎输入了一个过大的数字那么程序就会得到 `None` 值,你可以告诉用户当前 vector 元素的数量并再请求它们输入一个有效的值。这就比因为输入错误而使程序崩溃要友好的多!
当传给 `get` 方法的索引超出了 vector 的范围时,它不会 panic, 而是返回 `None` 。如果在正常情况下,访问超出 vector 范围的元素偶尔是可能发生的,那么你就会使用这种方法。此时你的代码可以像第六章讨论过的那样,处理 `Some(&element)` 和 `None` 两种情况。例如,索引可能来自用户输入的数字。如果用户不小心输入了一个过大的数字,程序就会得到 `None` ,这时你可以告诉用户当前 vector 中有多少项,并给他们一次重新输入有效值的机会。这就比因为一个输入错误而让程序崩溃更友好。
一旦程序获取了一个有效的引用,借用检查器将会执行所有权和借用规则(第四章讲到)来确保 vector 内容的这个引用和任何其他引用保持有效。回忆一下不能在相同作用域中同时存在可变和不可变引用的规则。这个规则适用于示例 8-6, 当我们获取了 vector 的第一个元素的不可变引用并尝试在 vector 末尾增加一个元素的时候,如果尝试在函数的后面再次引用这个元素是行不通的 :
当程序拿到了一个有效引用后,借用检查器会应用所有权和借用规则(第四章讲过),来确保这个对 vector 内容的引用以及其他任何引用都保持有效。回忆一下那条规则:在同一作用域中,不能同时拥有可变引用和不可变引用。这条规则就适用于示例 8-6: 我们持有了 vector 第一个元素的不可变引用,然后又尝试在 vector 末尾添加一个元素。如果还想在函数后面继续使用那个元素,这个程序就无法通过编译 :
```rust,ignore,does_not_compile
```rust,ignore,does_not_compile
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-06/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-06/src/main.rs:here}}
@ -79,13 +78,13 @@ Rust 提供了两种引用元素的方法的原因是当尝试使用现有元素
{{#include ../listings/ch08-common-collections/listing-08-06/output.txt}}
{{#include ../listings/ch08-common-collections/listing-08-06/output.txt}}
```
```
示例 8-6 中的代码看起来应该能够运行:为什么第一个元素的引用会关心 vector 结尾的变化?不能这么做的原因是由于 vector 的工作方式:在 vector 的结尾增加新元素时,在没有足够空间将所有元素依次相邻存放的情况下,可能会要求分配新内存并将老的元素拷贝到新的空间中。这时,第一个元素的引用就指向了被释放的内存。借用规则阻止程序陷入这种状 况。
示例 8-6 中的代码看起来似乎应该能工作:为什么对第一个元素的引用,会在乎 vector 末尾发生的变化呢?这是由 vector 的工作方式决定的。因为 vector 会把值彼此相邻地存放在内存中,所以如果末尾追加一个新元素,而当前存放位置又没有足够空间容纳所有元素,程序就可能需要分配一块新内存,并把旧元素复制到新空间里去。在这种情况下,原来指向第一个元素的引用就会指向已释放的内存。借用规则正是为了防止程序陷入这种情 况。
> 注意:关于 `Vec<T>` 类型的更多实现细节,请查看 [“The Rustonomicon”][nomicon]
> 注意:如果想了解 `Vec<T>` 类型更多的实现细节,请参阅 [“The Rustonomicon”][nomicon]。
### 遍历 vector 中的元素
### 遍历 vector 中的元素
如果想要依次访问 vector 中的每一个元素,我们可以遍历其所有的元素而无需通过索引一次一个的访问。示例 8-7 展示了如何使用 `for` 循环来获取 `i32` 值的 vector 中的每一个元素的不可变引用并将其打印 :
如果想依次访问 vector 中的每个元素,我们会遍历所有元素,而不是一次只通过索引访问一个。示例 8-7 展示了如何使用 `for` 循环,获取一个装有 `i32` 值的 vector 中每个元素的不可变引用,并把它们打印出来 :
```rust
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-07/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-07/src/main.rs:here}}
@ -93,7 +92,7 @@ Rust 提供了两种引用元素的方法的原因是当尝试使用现有元素
< span class = "caption" > 示例 8-7: 通过 `for` 循环遍历 vector 的元素并打印</ span >
< span class = "caption" > 示例 8-7: 通过 `for` 循环遍历 vector 的元素并打印</ span >
我们也可以遍历可变 vector 的每一个元素的可变引用以便能改变它们 。示例 8-8 中的 `for` 循环会给每一 个元素加 `50` :
我们也可以遍历可变 vector 中每个元素的可变引用,从而修改所有元素 。示例 8-8 中的 `for` 循环会给每个元素都 加上 `50` :
```rust
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-08/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-08/src/main.rs:here}}
@ -101,15 +100,15 @@ Rust 提供了两种引用元素的方法的原因是当尝试使用现有元素
< span class = "caption" > 示例 8-8: 遍历 vector 中元素的可变引用< / span >
< span class = "caption" > 示例 8-8: 遍历 vector 中元素的可变引用< / span >
为了修改可变引用所指向的值,在使用 `+=` 运算符之前必须使用解引用运算符(`*`)获取 `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 只能存储相同类型的值。这可能会带来不便;确实有些场景需要存放一组不同类型的值。幸运的是,枚举的各个变体都定义在同一个枚举类型之下,所以当我们需要用一个类型来表示不同种类的元素时,就可以定义并使用 枚举!
例如,假如我们想要从电子表格的一行中获取值,而这一行的有些列包含数字,有些包含浮点值,还有些是字符串。我们可以定义一个枚举,其成员会存放这些不同类型的值,同时所有这些枚举成员都会被当作相同类型:那个枚举的类型。接着可以创建一个储存该枚举值的 vector, 这样最终就能够储存不同类型的值了。示例 8-9 展示了这个用 法:
例如,假设我们想从电子表格的一行中读取值,而这一行中有些列包含整数,有些包含浮点数,还有些是字符串。我们可以定义一个枚举,让它的各个变体分别持有这些不同类型的值,而所有这些枚举变体都会被视为同一种类型,也就是该枚举本身的类型。然后,我们就可以创建一个存放这种枚举的 vector, 从而最终在其中保存不同类型的值。示例 8-9 展示了这种做 法:
```rust
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-09/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-09/src/main.rs:here}}
@ -117,15 +116,15 @@ vector 只能储存相同类型的值。这是很不方便的;绝对会有需
< span class = "caption" > 示例 8-9: 定义一个枚举, 以便能在 vector 中存放不同类型的数据< / span >
< span class = "caption" > 示例 8-9: 定义一个枚举, 以便能在 vector 中存放不同类型的数据< / span >
Rust 在编译时必须确切知道 vector 中的类型,这样它才能确定在堆上需要为每个元素分配多少内存。我们还必须明确这个 vector 中允许的类型。如果 Rust 允许 vector 存储任意类型,那么可能会因为一个或多个类型在对 vector 元素执行操作时导致(类型相关)错误。使用枚举加上 `match` 表达式意味着 Rust 会在编译时确保每种可能的情况都得到处理,正如第六章讲到的那样 。
Rust 必须在编译时知道 vector 中会有哪些类型,这样它才能准确知道在堆上存储每个元素需要多少内存。我们还必须明确指出这个 vector 允许哪些类型。如果 Rust 允许 vector 存放任意类型,那么在对 vector 元素执行操作时,就有可能因为某一种或多种类型而导致错误。使用枚举再配合 `match` 表达式,意味着 Rust 会像第六章所说的那样,在编译时确保每一种可能的情况都得到了处理 。
如果在编写程序时不能确切无遗地知道运行时会储存进 vector 的所有类型,枚举技术就行不通了。相反,你可以使用 trait 对象,第十八 章会讲到它。
如果在编写程序时,你并不知道运行时究竟会有哪些类型需要存进 vector, 那么这种枚举技巧就不适用了。相反, 你可以使用 trait 对象,第 18 章会讲到它。
现在我们了解了一些使用 vector 的最常见的方式,请一定去看看标准库中 `Vec` 定义的很 多其他实用方法的 [API 文档][vec-api]。例如,除了 `push` 之外还有一个 `pop` 方法,它 会移除并返回 vector 的最后一个元素。
现在我们已经讨论了一些最常见的 vector 用法,记得去看看标准库为 `Vec<T>` 定义的许 多其他实用方法的 [API 文档][vec-api]。例如,除了 `push` 之外, 还有一个 `pop` 方法会移除并返回 vector 的最后一个元素。
### 丢弃 vector 时也会丢弃其所有元素
### 丢弃 vector 时也会丢弃其所有元素
类似于任何其他的 `struct` , vector 在其离开作用域时会被释放,如示例 8-10 所标注的 :
和其他任何 `struct` 一样, vector 会在离开作用域时被释放,如示例 8-10 所标示的那样 :
```rust
```rust
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-10/src/main.rs:here}}
{{#rustdoc_include ../listings/ch08-common-collections/listing-08-10/src/main.rs:here}}
@ -133,11 +132,11 @@ Rust 在编译时必须确切知道 vector 中的类型,这样它才能确定
< span class = "caption" > 示例 8-10: 展示 vector 和其元素于何处被丢弃< / span >
< span class = "caption" > 示例 8-10: 展示 vector 和其元素于何处被丢弃< / span >
当 vector 被丢弃时,所有其内容也会被丢弃,这意味着这里它包含的整数将被清理。借用检查器确保了任何 vector 中内容的引用仅在 vector 本身有效时才可 用。
当 vector 被丢弃时,它包含的所有内容也都会被一并丢弃,这意味着它持有的整数会被清理掉。借用检查器会确保,对 vector 内容的任何引用都只会在 vector 本身有效时被使 用。
让我们继续下一个集合类型:`String`!
让我们继续下一个集合类型:`String`!
[data-types]: ch03-02-data-types.html#数据类型
[data-types]: ch03-02-data-types.html#数据类型
[nomicon]: https://doc.rust-lang.org/nomicon/vec/vec.html
[nomicon]: https://doc.rust-lang.org/nomicon/vec/vec.html
[vec-api]: https://doc.rust-lang.org/std/vec/struct.Vec.html
[vec-api]: https://doc.rust-lang.org/std/vec/struct.Vec.html
[deref]: ch15-02-deref.html#追踪指针 的值
[deref]: ch15-02-deref.html#追踪引用 的值