使用矢量值存储值的清单
Storing Lists of Values with Vectors
这里要看的第一个集合,便是 Vec<T>
,也叫做 矢量(vector) 类型。矢量类型允许将多个值,存储在单个的、将全部这些值挨个放入内存的数据结构中。矢量类型仅能存储同一类型的这些值。在有着某个数据项目清单,比如某个文件中的那些文本行,或购物车中那些货品价格时,那么矢量类型就是有用的。
创建一个新的矢量值
要创建出一个新的空矢量值,就要调用 Vec::new()
函数,如下清单 8-1 所示:
#![allow(unused)] fn main() { 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 就创建了一个新的、保存了值 1
、2
与 3
的 Vec<i32>
。之所以那个整数类型为 i32
,是由于 i32
正是默认的整数类型,如同第 3 章的 "数据类型" 中所讨论的那样。
#![allow(unused)] fn main() { let v = vec! [1, 2, 3]; }
清单 8-2:创建一个新的包含了值的矢量
由于这里已经给定了一些初始化 i32
的值,因此 Rust 就可以推断出 v
的类型为 Vec<i32>
,而那个类型注解就不是必要的了。接下来,就要看看怎样修改矢量。
更新某个矢量值
要创建出一个矢量,并随后将一些元素添加给他,就可以使用 push
方法,如下清单 8-3 中所示。
#![allow(unused)] fn main() { let mut v = Vec::new(); v.push(5); v.push(6); v.push(7); v.push(8); }
清单 8-3:使用 push
方法来把一些值添加到某个矢量
正如第 3 章中所讨论的,这里与任何变量一样,在想要能修改矢量的值时,就要使用 mut
关键字,将其构造为可变。这里在矢量内部的数字,全部都是 i32
类型,而 Rust 就会从这些数据,推断出这个类型,因此这里不需要 Vec<i32>
类型注解。
读取矢量的元素
Reading Elements of Vectors
引用存储在矢量中某个值的方式有两种:经由索引,或使用 get
方法。在接下来的示例中,为讲得更清楚的原因,已经对从这些方法返回的值进行了注释。
下面清单 8-5 给出了访问矢量某个值的两种方式,即索引语法与 get
方法。
#![allow(unused)] fn main() { let v = vec! [1, 2, 3, 4]; let third: &i32 = &v[2]; println! ("第三个元素为 {}", third); match v.get(2) { Some(third) => println! ("第三个元素为 {}", third), None => println! ("没有第三个元素。"), } }
清单 8-5:使用索引语法或 get
方法访问矢量中的某个元素
请留意这里的两个细节。首先,由于矢量是以从零开始的数字进行索引,因此这里使用了索引值 2
来获取那第三个元素。其次,这里是通过同时使用 &
与 []
,获取第三个元素的,这就给到一个引用变量,而使用带有作为参数传递索引的 get
方法,给到的却是个 Option<&T>
值。
Rust 提供这两种引用某个元素方式的原因在于,有了这两种方式,就可以在尝试使用某个超出了既有元素范围的索引值时,对程序此时的表现加以选择。比如,下面就来看看在有着一个五个元素的矢量,而随后尝试以两种技巧,来访问位于索引 100
处元素时,会发生什么事情,如下清单 8-6 中所示。
#![allow(unused)] fn main() { let v = vec! [1, 2, 3, 4, 5]; let does_not_exist = &v[100]; let does_not_exist = v.get(100); }
清单 8-6:尝试访问包含五个元素矢量中索引 100
处的元素
在运行此代码时,由于第一种 []
方式引用了不存在的元素,因此将导致程序死机。在有着某个对超出矢量末端的元素进行访问的尝试,而打算将程序崩溃掉时,那么用这种方式是最佳的。
而在传递给 get
方法的索引,位于矢量外部时,他就会返回不会程序死机的 None
值。在寻常情况下,就会时不时出现对超出矢量范围元素的访问时,就应使用这种方式。这时代码就将有如同第 6 章中所讨论的,处理 Some(&element)
或 None
值的逻辑。比如,那个索引可以是来自某人输入的数字。在他们不小心输入了一个过大的数字时,程序就会得到一个 None
值,这时就可以告诉用户在当前矢量中有多少个项目,并给到他们又一次输入有效值的机会。比起由于输入错误而将程序崩溃掉,这将是更加用户友好!
当程序有了有效引用时,Rust 的借用检查器,就会强制执行所有权与借用规则检查(在第 4 章讲到过),来确保该引用及全部其他的、到这个矢量内容的引用是有效的。请回顾那条表明了不能在同一作用域中,有着多个可变与不可变引用的规则。那条规则就适用于下面清单 8-7,清单中有着一个到矢量首个元素的可变引用,并尝试将一个元素添加到示例末尾。若同时在那个函数中尝试引用那个元素,那么该程序就不会工作:
#![allow(unused)] fn main() { let mut v = vec! [1, 2, 3, 4, 5]; let first: &i32 = &v[0]; v.push(6); println! ("首个元素为:{}", first); }
清单 8-7:在保留到矢量某个条目的引用同时,尝试将一个元素添加到该矢量
对此代码进行编译,将引发下面这个错误:
$ cargo run ✔
Compiling vec_demo v0.1.0 (/home/peng/rust-lang/projects/vec_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 `vec_demo` due to previous error
清单 8-7 中的代码看起来似乎可以工作:为何到矢量首个元素的引用,会牵连到该矢量末尾的变化呢?这个报错是由于矢量工作原理:由于矢量是将他的那些值,挨个放在内存中的,那么将新元素添加到矢量末尾,而在该矢量当前存储处,没有足够场所来挨个放置全部这些元素时,这时就会要求分配新内存,并将那些旧有元素拷贝到新内存空间。在那种情况下,到首个元素的引用就会指向已解分配内存(deallocated memory)。而正是这些 Rust 的借用规则,防止程序已这样的情形而告终。
请注意:更多有关
Vec<T>
类型的实现细节,请参考 Rust 专论(The Rustonomicon)。
迭代矢量中的值
Iterating over the Values in a Vector
要依次访问矢量中的各个元素,就要迭代全部元素,而非使用那些索引值,一次访问一个了。下面清单 8-8 展示了怎样使用 for
循环,来获取到一个 i32
矢量值中各个元素的不可变引用,并将这些元素打印出来。
#![allow(unused)] fn main() { let v = vec! [100, 32, 57]; for i in &v { println! ("{}", i); } }
清单 8-8:通过使用 for
循环对各个元素进行迭代,而打印出矢量中的每个元素
也可以为了对全部元素进行修改,而对可变示例中的各个元素,进行可变引用的迭代。下面清单 8-9 中的 for
循环,将把 50
添加到各个元素。
#![allow(unused)] fn main() { let mut v = vec! [100, 32, 57]; for i in &mut v { *i += 50; } }
清单 8-9:对实例中各个元素,进行可变引用的迭代
要修改可变引用所指向的值,就必须使用 *
解引用操作符(the *
dereference operator),在能够使用 +=
运算符之前,获取到 i
中的那个值。在后面第 15 章的 “以解引用操作符,顺着指针找到值” 小节,就会讲到这个解引用操作符。
运用枚举存储多种类型
Using an Enum to Store Multiple Types
矢量只能存储同一类型的值。这就会不方便;显然是有需要存储不同类型条目清单的使用场景。幸运的是,枚举的那些变种,就是定义在同一枚举类型之下的,那么在需要某种表示那些不同类型元素的类型时,就可以定义并使用一个枚举!
好比说这里要从电子表格的某行,其中该行的一些列包含了整数,另一些列包含了浮点数,而其他列则包含了字符串,而要从这行获取到这些数据。那么就可以定义这样一个枚举,他的那些变种将保存这些不同值类型,而全部这些变种,就会被看着是同一类型:即为该枚举。随后就可以创建一个矢量,来保存那个枚举,进而最终保存了这些不同类型。下面清单 8-10 中就对此进行了演示。
#![allow(unused)] 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-10:定义一个 enum
来在矢量中存储不同类型的值
Rust 需要在编译时了解那个矢量中会有些什么类型,这样他就清楚存储该矢量的每个元素,所需要的内存堆上内存准确数量。同时必须显式声明该矢量中允许哪些类型。若 Rust 允许矢量保存任意类型,那么就会存在一个或多个类型,将引发在该矢量的元素上执行操作错误的可能。运用枚举加上 match
表达式,就意味着 Rust 将在编译时,确保所有可能情形都被处理,如同第 6 章中讨论的那样。
但若不清楚运行时程序会在矢量中收到的详尽类型集合,那么这个枚举技巧就不会有用。相反,这个时候就可以使用特质对象(a trait object),而在后面的第 17 章就会讲到特质对象。
既然这里已经讨论了使用矢量的一些最常用方式,那就一定要看看 API 文档,了解那些定义在 Vec<T>
上,由标准库所定义的全部有用方法。比如,除了 push
之外,pop
方法会移除并返回矢量的最后一个元素。
丢弃某个矢量,就会丢弃他的元素
Dropping a Vector Drops Its Elements
与其他任何 struct
一样,矢量在超出作用域时,就会被释放掉,如下清单 8-4 所示。
#![allow(unused)] fn main() { { let v = vec! [1, 2, 3, 4]; // 对 v 执行一些操作 } // 这里 v 就超出了作用域,而被释放掉 }
清单 8-4:对矢量及其元素在何处被丢弃进行展示
在这个矢量被丢弃时,那么他所有内容也会被丢弃,即他保存的那些整数将被清理掉。这初一看似乎直接明了,然而在开始触及到一些到该矢量元素的引用时,事情就会变得复杂。接下来就要解决这个问题!
下面就移步到下一种集合类型:String
吧!