使用向量存储值列表
我们要看的第一个集合类型是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
,2
和3
.整数类型为i32
因为这是默认的整数类型,正如我们在“数据
Types“部分。
fn main() { let v = vec![1, 2, 3]; }
示例 8-2:创建一个包含 值
因为我们在i32
值,Rust 可以推断v
是Vec<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 中,for
loop 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 包含任何类型,则有可能一个或多个
这些类型会导致对
向量。使用枚举加上match
expression 表示 Rust 将确保
在编译时处理所有可能的情况,如 Chapter 6 所述。
如果您不知道程序在运行时将获取的详尽类型集 store 在 vector 中,枚举技术将不起作用。相反,您可以使用 trait object,我们将在第 17 章中介绍。
现在我们已经讨论了一些最常见的使用 vector 的方法,请确保
查看所有 API 文档
定义 上的 有用方法Vec<T>
由 Standard 库。例如,在
添加到push
一个pop
method 删除并返回最后一个元素。
删除 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/)为准