使用向量存储值列表

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

创建新向量

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

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

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

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

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

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

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

因为我们已经给出了初始值,所以 Rust 可以推断出 的类型是 ,并且类型注释不是必需的。接下来,我们将了解如何 以修改向量。i32vVec<i32>

更新 Vector

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

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

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

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

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

读取向量的元素

有两种方法可以引用存储在 vector 中的值:通过索引或通过 using the method.在下面的示例中,我们注释了 从这些函数返回的值,以便更加清晰。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 中的项目

请注意此处的一些详细信息。我们使用 的 index 值来获取第三个元素 因为 Vector 是按数字索引的,从 0 开始。Using and 为我们提供了对 index 值处元素的引用。当我们使用将索引作为参数传递的方法时,我们会得到一个我们可以 与 一起使用。2&[]getOption<&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[]

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

当程序具有有效的引用时,借用检查器会强制执行 所有权和借款规则(在第 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 的末尾可能需要 分配新内存并将旧元素复制到新空间(如果有 没有足够的空间将所有元素彼此相邻放置,其中向量 当前已存储。在这种情况下,对第一个元素的引用将是 指向已释放的内存。借用规则阻止程序 最终陷入那种境地。

注意:有关该类型的实现详细信息的更多信息,请参阅“该 Rustonomicon”。Vec<T>

迭代 vector 中的值

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

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

示例 8-7:通过 使用 for 循环遍历元素

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

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

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

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

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

使用枚举存储多种类型

向量只能存储相同类型的值。这可以是 不便;肯定有一些用例需要存储 不同类型的项目。幸运的是,枚举的变体已定义 在同一个 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:定义一个枚举来存储 一个向量中的不同类型

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

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

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

删除 Vector 会删除其元素

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

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/)为准