# 以矢量值存储值列表

我们将探讨的首个集合类型为 `Vec<T>`，也称为矢量。矢量值允许咱们存储多个值于单一数据结构中，依次存放所有值于内存中。矢量只能存储同一类型的值 <sup>1</sup>。当咱们有着储项目列表，比如文件中的文本行或购物车中的商品价格时，他们非常有用。

> **译注**：
>
> <sup>1</sup>，与后面的 [哈希图](./hash_maps.md) 一样，矢量值属于同构的，homogeneous。


## 创建一个新的矢量值

要创建一个新的空矢量值，我们调用 `Vec::new` 函数，如下清单 8-1 中所示。

<a name="listing_8-1"></a>
```rust
    let v: Vec<i32> = Vec::new();
```

**清单 8-1**：创建一个新的空矢量值，来保存类型 `i32` 的值

请注意，我们在这里添加了类型注解。因为我们没有插入值到这个矢量，Rust 不知道我们打算存储何种类型的元素。这属于一个要点。矢量是使用泛型实现的；我们将在第 10 章中介绍如何对咱们自己的类型使用泛型。目前，要知道由标准库提供的 `Vec<T>` 类型，可以保存任何类型。当我们创建一个矢量值来保存某一特定类型时，我们可在尖括号里指定该类型。在清单 8-1 中，我们已告诉 Rust，（变量）`v` 中的 `Vec<T>` 将存储 `i32` 类型的元素。

更常见的是，咱们将以一些初始值创建 `Vec<T>`，而 Rust 将推断出咱们打算存储的值类型，因此咱们很少需要进行这一类型注解。Rust 善解人意地提供了宏 `vec!`，他将创建一个新的矢量值，存储咱们给他的值。下面清单 8-2 创建了个新的 `vec<i32>`，存储值 `1`、`2` 及 `3`。整数类型为 `i32`，因为这是默认的整数类型，正如我们在第 3 章的 [“数据类型”](../programming_concepts/data_types.md) 小节讨论的那样。


<a name="listing_8-2"></a>
```rust
    let v = vec! [1, 2, 3];
```

**清单 8-2**：创建一个包含值的新矢量值


因为我们已给出了初始的 `i32` 值，Rust 可推断出 `v` 的类型为 `Vec<i32>`，因此类型注解是不必要的。接下来，我们将讨论怎样修改矢量值。


## 更新矢量值

要创建一个矢量值然后添加元素到他，我们可使用 `push` 方法，如下清单 8-3 中所示。

<a name="listing_8-3"></a>
```rust
    let mut v = Vec::new();

    v.push(5);
    v.push(6);
    v.push(7);
    v.push(8);
```

**清单 8-3**：使用 `push` 方法添加值到矢量


与任何变量一样，当我们希望能够修改其值时，我们需要使用 `mut` 关键字构造其为可变，正如在 [第 3 章中曾讨论过的](../programming_concepts/variables_and_mutability.md)。我们放入其中的数字均为 `i32` 类型，而 Rust 会从数据推断出这点，因此我们无需 `Vec<i32>` 这个注解。


## 读取矢量的元素

引用存储在矢量中值的方式有两种：

- 经由索引；
- 或通过使用 `get` 方法。

在以下示例中，出于更清晰目的，我们已注解了返回自这些函数的值的类型。


下面清单 8-4 同时展示了访问矢量中值的两种方法，即索引语法与 `get` 方法。

<a name="listing_8-4"></a>
```rust
    let v = vec! [1, 2, 3, 4];

    let third: &i32 = &v[2];
    println! ("第三个元素为 {third}");

    let third: Option<&i32> = v.get(2);
    match third {
        Some(third) => println! ("第三个元素为 {third}"),
        None => println! ("没有第三个元素。"),
    }
```

**清单 8-4**：使用索引语法和使用 `get` 方法，分别访问矢量中的项目

> **译注**：与 `get` 一样，`[]` 这个运算符同样是个方法。

请注意这里的几处细节。我们使用索引值 `2` 来获取第三个元素，因为矢量是按数字索引的，从零开始。使用 `&` 及 `[]` 给到我们到索引值处的元素的引用。当我们以一个作为参数传递的索引使用 `get` 方法时，我们会得到一个 `Option<&T>`，我们可将其与 `match` 一起使用。

Rust 提供了这两种引用元素的方式，以便在咱们尝试使用超出既有元素范围的索引值时，咱们可以选择程序的行事方式。举个例子，我们来看看当我们有个五个元素的矢量，然后尝试分别以两种技巧访问索引 `100` 处元素时，会发生什么，如下清单 8-5 中所示。

<a name="listing_8-5"></a>
```rust
    let v = vec! [1, 2, 3, 4, 5];

    let does_not_exist = &v[100];
    let does_not_exist = v.get(100);
```

**清单 8-5**：尝试访问五个元素的矢量中索引 `100` 处的元素

当我们运行这段代码时，第一个 `[]` 方法将导致程序终止运行，因为他引用了不存在的元素。当咱们希望咱们的程序在尝试访问超出矢量末尾的元素时崩溃时，就最好使用这个方法。

当传递了超出矢量范围的索引给 `get` 方法时，他会在不终止运行下返回 `None`。当访问超出矢量范围的元素在正常情况下偶尔会发生时，咱们将使用这个方法。咱们的代码随后将有处理要么 `Some(&element)` 要么 `None` 的代码，如同在 [第 6 章中讨论过的](../enums_and_pattern_matching/match_control_flow.md) 那样。例如，索引可能来自输入数字的人。当他们不小心输入了太大的数字，进而程序得到 `None` 值时，咱们可以告诉用户当前矢量中有多少个项目，并给他们另一次输入有效值的机会。这比由于输入错误而导致程序崩溃更加用户友好！

当程序有个有效的引用时，借用检查器会强制执行所有权及借用的规则检查（[第 4 章](../ownership/references_and_borrowing.md) 中已介绍），确保该引用以及到矢量内容的任何其他引用都保持有效。请回顾那条规定了咱们不能在同一作用域中有可变和不可变引用的规则。这条规则适用于下面清单 8-6，其中我们持有一个到矢量中第一个元素的不可变引用，并尝试添加一个元素到末尾。若我们稍后还尝试在函数中引用该元素，那么这个程序将不工作。

<a name="listing_8-6"></a>
```rust
    let mut v = vec! [1, 2, 3, 4, 5];

    let first: &i32 = &v[0];

    v.push(6);

    println! ("首个元素为：{first}");
```

*清单 8-6：在保留到矢量某个条目的引用同时，尝试将一个元素添加到该矢量*

编译这段代码将导入如下报错：

```console
$ cargo run
   Compiling vector_demo v0.1.0 (/home/hector/rust-lang-zh_CN/projects/vector_demo)
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:5
  |
4 |     let first: &i32 = &v[0];
  |                        - immutable borrow occurs here
5 |
6 |     v.push(6);
  |     ^^^^^^^^^ mutable borrow occurs here
7 |
8 |     println! ("首个元素为：{first}");
  |                             ----- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `vector_demo` (bin "vector_demo") due to 1 previous error
```

清单 8-6 中的代码可能看起来应该工作：为什么到第一个元素的引用应该关心矢量末尾的变化呢？这个报错是由于矢量的工作方式：因为矢量彼此相邻地把值放入内存，所以在没有足够空间来彼此相邻地放置所有元素于该矢量当前存储之处时，添加新的元素到矢量的末尾就可能需要分配新的内存并拷贝原有元素到新的空间。在这种情况下，到第一个元素的引用将指向已解分配（释放）的内存。借用规则阻止程序陷入这种情况。


> **注意**：有关 `Vec<T>` 类型实现细节的更多信息，请参考 [Rust 专论，The Rustonomicon](https://doc.rust-lang.org/nomicon/vec/vec.html)。


## 迭代矢量中的值

为了依次访问矢量中的每个元素，我们将遍历所有元素，而不是使用索引一次访问一个元素。下面清单 8-7 展示了怎样使用 `for` 循环，获取到 `i32` 值矢量中各个元素的不可变引用并打印他们。


<a name="listing_8-7"></a>
```rust
    let v = vec! [100, 32, 57];

    for i in &v {
        println! ("{i}");
    }
```

**清单 8-7**：通过使用 `for` 循迭代元素，打印矢量中的各个元素

为了对所有元素进行更改，我们还可迭代到可变矢量中各个元素的可变引用。下面清单 8-8 中的 `for` 循环将加 `50` 到每个元素。


<a name="listing_8-8"></a>
```rust
    let mut v = vec! [100, 32, 57];

    for i in &mut v {
        *i += 50;
    }
```

**清单 8-8**：迭代到矢量中元素的可变引用

为了修改可变引用指向的值，我们必须在可以使用 `+=` 运算符前，先使用 `*` 解引用运算符，the `*` dereference operator，获取 `i` 中的值。我们将在第 15 章的 [“根据引用得到值”](../smart_pointers/deref-t.md#根据引用得到值) 小节进一步讨论解引用运算符。

由于借用检查器的规则，无论是可变地还是不可变地迭代矢量都是安全的。若我们在列表 8-7 与列表 8-8 中的 `for` 循环体中，尝试插入或移除元素，我们将得到与我们在列表 8-6 中的代码下，得到的类似编译器报错。到 `for` 循环持有的矢量的引用，阻止了对整个矢量的同时修改。


## 使用枚举存储多种类型

矢量值只能存储同一类型的值。这会带来不便；必然存在需要存储不同类型项目列表的用例。幸运的是，枚举的变种都定义在同一枚举类型下，因此当我们需要一种类型来表示不同类型的元素时，我们可以定义并使用枚举！

例如，假设我们想要获取电子表格某行中的值，其中该行中某些列包含整数、浮点数与字符串。我们可以定义一个枚举，其变种将保存不同的值类型，而所有枚举变种都将被视为同一类型：即这个枚举。然后，我们可以创建一个矢量来保存这个枚举，进而最终保存不同类型。我们已在下面清单 8-9 中演示了这点。

<a name="listing_8-9"></a>
```rust
    enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
    }

    let row = vec! [
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Text(String::from("blue")),
        SpreadsheetCell::Float(10.12),
    ];
```

**清单 8-9**：定义一个枚举以在一个矢量值中存储不同类型的值

Rust 需要在编译时知道哪些类型将在矢量值中，以便他准确地知道将需要堆上的多少内存来存储各个元素。我们还必须显式指出该矢量中允许哪些类型。若 Rust 允许矢量值存储任何类型，那么一种或多种类型将可能造成对矢量值元素执行的操作中的错误。使用枚举加上 `match` 表达式，意味着 Rust 将在编译时确保每种可能情形都得以处理，正如第 6 章中曾讨论的那样。

当咱们不知道程序在运行时将获得的，要存储在矢量中的详尽类型集时，这种枚举技巧将不起作用。相反，咱们可以使用 [特质对象](../oop/trait_objects.md)，我们将在第 18 章中介绍他。

现在我们已经讨论了使用矢量值的一些最常见方式，请务必查看 [API 文档](https://doc.rust-lang.org/std/vec/struct.Vec.html)，了解由标准库定义在 `Vec<T>` 上的所有实用方法。例如，除了 `push` 方法外，`pop` 方法会移除并返回最后一个元素。


## 弃用矢量值会弃用其元素

与任何其他 `struct` 一样，矢量值在其超出作用域时也会被释放，如下清单 8-10 中注解的那样。


<a name="listing_8-10"></a>
```rust
    {
        let v = vec! [1, 2, 3, 4];

        // 对 v 执行一些操作
    } // 此处 v 超出作用域而被释放
```

**清单 8-10**：展示矢量值及其元素于何处被弃用


当这个矢量值被弃用时，其所有内容也会被弃用，这意味着他保存的整数将被清理。借用检查器确保到矢量值内容的任何引用都只会在该矢量值本身有效时被使用。

我们来继续讨论下一种集合类型： `String`！



（End）


