使用向量存储值列表

我们要看的第一个集合类型是Vec<T>,也称为向量。 向量允许您在单个数据结构中存储多个值,该数据结构 将所有值放在内存中彼此相邻。向量只能存储值 相同类型。当您有一个项目列表时,它们非常有用,例如 文件中的文本行或购物车中商品的价格。

创建新向量

要创建一个新的空 vector,我们调用Vec::new函数,如 示例 8-1.

fn main() {
    let v: Vec<i32> = Vec::new();
}

示例 8-1:创建一个新的空 vector 来保存值 的类型i32

请注意,我们在此处添加了类型注释。因为我们没有插入任何 values 添加到这个 vector 中,Rust 不知道我们打算用什么样的元素 商店。这是很重要的一点。向量是使用泛型实现的; 我们将在第 10 章介绍如何将泛型与你自己的类型一起使用。目前, 要知道Vec<T>type 可以包含任何类型。 当我们创建一个 vector 来保存特定类型时,我们可以在 尖括号。在示例 8-1 中,我们告诉 Rust 的Vec<T>v将 hold 元素的i32类型。

更常见的是,您将创建一个Vec<T>替换为初始值,Rust 将推断 要存储的值的类型,因此您很少需要执行此类型 注解。Rust 方便地提供了vec!宏,这将创建一个 new vector 来保存你给它的值。示例 8-2 创建了一个新的Vec<i32>,保存1,23.整数类型为i32因为这是默认的整数类型,正如我们在“数据 Types“部分。

fn main() {
    let v = vec![1, 2, 3];
}

示例 8-2:创建一个包含 值

因为我们在i32值,Rust 可以推断vVec<i32>,并且类型注释不是必需的。接下来,我们将了解如何 以修改向量。

更新 Vector

要创建一个 vector 然后向其添加元素,我们可以使用push方法 如示例 8-3 所示。

fn main() {
    let mut v = Vec::new();

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

示例 8-3:使用push将值添加到 向量

与任何变量一样,如果我们想能够更改其值,我们需要 使其可变mut关键字,如第 3 章所述。数字 我们放在里面都是类型i32,而 Rust 从数据中推断出这一点,因此 我们不需要Vec<i32>注解。

读取向量的元素

有两种方法可以引用存储在 vector 中的值:通过索引或通过 使用get方法。在下面的示例中,我们注释了 从这些函数返回的值,以便更加清晰。

示例 8-4 显示了访问 vector 中值的两种方法,其中索引 语法和get方法。

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    let third: &i32 = &v[2];
    println!("The third element is {third}");

    let third: Option<&i32> = v.get(2);
    match third {
        Some(third) => println!("The third element is {third}"),
        None => println!("There is no third element."),
    }
}

示例 8-4:使用索引语法和使用get访问 vector 中项目的方法

请注意此处的一些详细信息。我们使用2获取第三个元素 因为 Vector 是按数字索引的,从 0 开始。Using and 为我们提供了对 index 值处元素的引用。当我们使用&[]get方法,我们得到一个Option<&T>我们可以 使用match.

Rust 提供了这两种引用元素的方法,因此你可以选择如何 程序在尝试使用超出 现有元素。例如,让我们看看当我们有一个 vector 时会发生什么 的 5 个元素,然后我们尝试访问索引为 100 的元素,每个元素 技术,如示例 8-5 所示。

fn main() {
    let v = vec![1, 2, 3, 4, 5];

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

示例 8-5:尝试访问 index 处的元素 100 在包含 5 个元素的向量中

当我们运行这段代码时,第一个方法会导致程序 panic ,因为它引用了一个不存在的元素。此方法最适合在以下情况下使用 希望你的程序在尝试访问超过 向量的 end 的 end 的 Vector 的 end 的 T[]

get方法传递一个 vector 之外的索引,则返回None没有惊慌。如果访问元素 超出向量范围时,正常情况下可能偶尔发生 情况 下。然后,您的代码将具有逻辑来处理Some(&element)None,如第 6 章所述。例如,索引 可能来自输入数字的人。如果他们不小心输入了 number 的None值,您可以告诉 用户当前向量中有多少项,并给他们另一个机会 输入有效值。这比使程序崩溃更用户友好 由于拼写错误!

当程序具有有效的引用时,借用检查器会强制执行 所有权和借款规则(在第 4 章中介绍)以确保此参考 并且对向量内容的任何其他引用仍然有效。回想一下 规则,规定你不能在同一 范围。这条规则在示例 8-6 中适用,其中我们持有一个不可变的引用 添加到 vector 中的第一个元素,并尝试在末尾添加一个元素。这 如果我们稍后还尝试在 功能。

fn main() {
    let mut v = vec![1, 2, 3, 4, 5];

    let first = &v[0];

    v.push(6);

    println!("The first element is: {first}");
}

示例 8-6:尝试向 vector 添加元素 持有对项目的引用时

编译此代码将导致此错误:

$ cargo run
   Compiling collections v0.1.0 (file:///projects/collections)
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
 --> src/main.rs:6:5
  |
4 |     let first = &v[0];
  |                  - immutable borrow occurs here
5 |
6 |     v.push(6);
  |     ^^^^^^^^^ mutable borrow occurs here
7 |
8 |     println!("The first element is: {first}");
  |                                     ------- immutable borrow later used here

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

示例 8-6 中的代码可能看起来应该可以工作:为什么引用应该 到第一个元素关心向量末尾的变化吗?此错误为 由于 Vector 的工作方式:因为 Vector 将值彼此相邻 在内存中,将新元素添加到 vector 的末尾可能需要 分配新内存并将旧元素复制到新空间(如果有 没有足够的空间将所有元素彼此相邻放置,其中向量 当前已存储。在这种情况下,对第一个元素的引用将是 指向已释放的内存。借用规则阻止程序 最终陷入那种境地。

注意:有关Vec<T>类型中,请参阅” Rustonomicon”。

迭代 vector 中的值

要依次访问向量中的每个元素,我们将遍历所有 元素,而不是使用索引一次访问一个。示例 8-7 显示了如何作 要使用for循环获取对 向量i32值并打印它们。

fn main() {
    let v = vec![100, 32, 57];
    for i in &v {
        println!("{i}");
    }
}

示例 8-7:通过 使用for

我们还可以迭代对可变 vector 中每个元素的可变引用 以便对所有元素进行更改。这for示例 8-8 中的 loop 将添加50添加到每个元素。

fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }
}

示例 8-8:迭代对 向量中的元素

要更改可变引用引用的值,我们必须使用 dereference 运算符来获取*i在我们可以使用 Operator 之前。我们将在“在 指向 Value with the Dereference Operator“ 部分的指针 15.+=

迭代 vector(无论是不可变的还是可变的)都是安全的,因为 借用检查器的规则。如果我们尝试在for循环体在示例 8-7 和示例 8-8 中,我们会得到一个编译器错误 类似于示例 8-6 中的代码。对 vector 中,forloop holds 可防止同时修改 整个向量。

使用枚举存储多种类型

向量只能存储相同类型的值。这可以是 不便;肯定有一些用例需要存储 不同类型的项目。幸运的是,枚举的变体已定义 在同一个 enum 类型下,所以当我们需要一个类型来表示 不同的类型,我们可以定义并使用一个 enum!

例如,假设我们想从电子表格中的一行中获取值,其中 行中的一些列包含整数,一些浮点数, 和一些字符串。我们可以定义一个枚举,其变体将保存不同的 value 类型,并且所有 enum 变体都将被视为相同的类型:即 枚举。然后我们可以创建一个 vector 来保存该枚举,因此,最终, 持有不同类型的我们已经在示例 8-9 中演示了这一点。

fn main() {
    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:定义enum要存储 一个向量中的不同类型

Rust 需要知道编译时 vector 中将包含哪些类型,以便知道 确切地说明存储每个元素需要多少内存。我们 还必须明确说明此 vector 中允许的类型。如果 Rust 允许 vector 包含任何类型,则有可能一个或多个 这些类型会导致对 向量。使用枚举加上matchexpression 表示 Rust 将确保 在编译时处理所有可能的情况,如 Chapter 6 所述。

如果您不知道程序在运行时将获取的详尽类型集 store 在 vector 中,枚举技术将不起作用。相反,您可以使用 trait object,我们将在第 17 章中介绍。

现在我们已经讨论了一些最常见的使用 vector 的方法,请确保 查看所有 API 文档 定义 上的 有用方法Vec<T>由 Standard 库。例如,在 添加到push一个popmethod 删除并返回最后一个元素。

删除 Vector 会删除其元素

与任何其他struct时,向量在超出范围时被释放,因为 在示例 8-10 中注释。

fn main() {
    {
        let v = vec![1, 2, 3, 4];

        // do stuff with v
    } // <- v goes out of scope and is freed here
}

示例 8-10:显示 vector 及其元素的位置 被丢弃

当 vector 被丢弃时,其所有内容也会被丢弃,这意味着 它保存的整数将被清理。借用检查器确保任何 对 vector 内容的引用仅在 vector 本身为 有效。

让我们继续下一个集合类型:String!

本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准