8.1 向量Vector
8.1.0. 本章内容
第八章主要讲的是 Rust 中常见的集合。Rust 提供了很多集合类型的数据结构,这些集合可以包含很多值。但是第八章所讲的集合与数组和元组有所不同。
第八章中的集合是存储在堆内存上而非栈内存上的,这也意味着这些集合的数据大小无需在编译时就确定,在运行时它们可以动态地变大或变小。
本章主要会讲三种集合:Vector(本文)、String 和 HashMap。
8.1.1. 使用 Vector 存储多个值
Vector 这个类型的写法是 Vec<T>,其中 T 代表泛型类型参数,在实际使用时替换成自己需要的数据类型即可。
Vector 由标准库提供,在 Vector 里可以存储多个相同类型的值,它们在内存中是连续存放的。可以把它视为可扩展的数组。
创建 Vector 可以使用 Vec::new 这个函数,我们看个例子:
fn main() {
let v: Vec<i32> = Vec::new();
let v = vec![1, 2, 3];
let v = Vec::with_capacity(10);
}
let v: Vec<i32> = Vec::new(): 使用Vec::new声明了一个元素类型为i32的Vector(常用)。let v = vec![1, 2, 3]: 使用vec!宏创建带初始值的Vector。这里在声明时已经把1, 2, 3填进了向量。当然只使用vec![]不填内容也是可以的(常用)。let v = Vec::with_capacity(10): 创建空 Vector,但提前分配至少 10 个元素的容量。适合你已经知道大概要放多少元素,能减少扩容次数,提高性能。
第一种方法(Vec::new())需要显式声明类型为 Vec<i32>,是因为 Vec::new() 创建的是一个空的 Vector,里面没有元素;又因为这个例子里没有前后文供 Rust 推断,所以 Rust 就推断不出来元素类型,就会报错。如果有前后文供 Rust 推断,Rust 就能够自行判断 Vector 里的元素类型。
第二种方法(vec![])就不需要显式声明元素类型了,因为 Rust 编译器根据初始值推断出了元素类型是 i32。
8.1.2. 更新 Vector
1. 添加元素
向 Vector 末尾添加元素使用 push 方法。如下例:
fn main() {
let mut v = Vec::new();
v.push(1);
}
- 注意:向
Vector里添加元素的前提是这个Vector是可变变量,所以在声明的时候需要mut关键字。 - 这里的
let mut v = Vec::new();也没有显式声明元素类型,但是 Rust 编译器通过下文向Vector里添加1的操作推断出了元素类型是i32。
还有其他的一些添加元素的方法:
fn main() {
let mut v = Vec::new();
v.extend([1, 2, 3]); // 批量插入
v.insert(1, 99); // 在指定下标插入(越界会 panic)
let mut a = vec![1, 2, 3];
let mut b = vec![4, 5, 6];
a.append(&mut b); // 把 `b` 的所有元素移动到 `a`(`b` 会变空)
}
2. 从 Vector 中删除元素
pop():删除尾部元素,并返回被 Option 包裹的值(在 6.2. Option 枚举 中讲过)。如果为空,返回 None。
fn main() {
let mut v = vec![1, 2, 3];
let x = v.pop(); // 返回 Some(3)
}
remove(index):删除指定位置的元素,并返回它。后面的元素会前移。index 越界会 panic。
fn main() {
let mut v = vec![1, 2, 3];
let x = v.remove(1); // 返回 2(v 变为 [1, 3])
}
clear():清空所有元素。长度变成 0,但容量仍然保留。
fn main() {
let mut v = vec![1, 2, 3];
v.clear(); // v 现在是 []
}
与任何其他的 struct 结构体一样,当 Vector 离开作用域后,它和它里面的元素就自然会被清理掉。
3. 读取 Vector 的元素
一共有两种方式可以访问 Vector 里面的值:使用索引,或者使用 get 方法。如下例:一个 Vector 里面存有 [1, 2, 3, 4, 5],访问并打印出第三个元素。
fn main() {
let v = vec![1, 2, 3, 4, 5];
let third = &v[2]; // 索引
println!("The third element is {}", third);
match v.get(2) { // get 方法加 match
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element."),
};
}
let third = &v[2];:使用索引访问位置 2 的元素(第三个元素)。前面的&表示引用。v.get(2):使用get方法来读取。由于返回值是Option类型,所以要使用match(在 6.3. 控制流运算符-match 中讲过)来解包。如果能取到值,就会绑定给third并打印;如果不能(None),就会打印 “There is no third element.”。
这两种方法效果一样,但对非法访问(例如索引越界)的处理不同。
先试试使用索引(非法访问):
fn main() {
let v = vec![1, 2, 3, 4, 5];
let third = &v[100]; // 索引 100 越界了
println!("The third element is {}", third);
}
输出:
thread 'main' panicked at src/main.rs:3:19:
index out of bounds: the len is 5 but the index is 100
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
程序触发了 panic!,终止执行。
再试试使用 get(非法访问):
fn main() {
let v = vec![1, 2, 3, 4, 5];
match v.get(100) { // 索引 100 越界了
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element."),
};
}
输出:
There is no third element.
因为 get 不能从索引 100 上获取东西,所以它就会返回 None。
使用建议:遇到越界时想直接触发 panic! 结束程序,就用索引;其余情况优先用 get 做安全处理。
8.1.3. 所有权和借用规则
还记得在 4.2. 所有权规则、内存与分配 中讲的借用规则吗?同一个作用域内不能同时有可变和不可变引用。这个规则在 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);
}
输出:
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let first = &v[0];
| - immutable borrow occurs here
4 | v.push(6);
| ^^^^^^^^^ mutable borrow occurs here
5 | println!("The first element is {}", first);
| ----- immutable borrow later used here
push函数的签名是&mut self, value: T,&mut表示push会把传进来的变量作为可变引用来处理。在例子中就是v在这里有一个可变引用。let first = &v[0];这里的first是v的不可变引用,两者又在同一个作用域下,所以会报错。println!会把传进去的变量作为不可变引用。
在这个作用域内同时出现了可变和不可变引用,所以程序会报错。
但有人可能会疑惑——push 是往 Vector 的后面加东西,前面的元素不会受影响,为什么 Rust 要搞这么麻烦的设计?
这是因为在内存中 Vector 的元素是连续存储的。如果往后面加一个元素,正好又有东西占用了后面的内存,腾不出地方放新的元素,系统就得重新分配内存,找个足够大的地方来放置添加了元素之后的 Vector。这样的话,原来的那块内存就会被释放或者重新分配掉,但引用仍然会指向原先的内存地址,造成悬空引用(在 4.4. 引用与借用 中有讲)。
8.1.4. 遍历 Vector 里的值
使用 for 循环是最常见的方法。如下例:
fn main() {
let v = vec![1, 2, 3, 4, 5];
for i in &v {
println!("{}", i);
}
}
输出:
1
2
3
4
5
当然,如果想要在循环里修改元素也是可以的,只需要把 v 声明成可变的,把 &v 改成 &mut v 即可:
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
for i in &mut v {
*i += 10;
}
for i in v {
println!("{}", i);
}
}
注意:第四行的 *i 前面之所以有个 *,是因为 i 本质上是 &mut i32 类型,存储的是指针而不是实际的 i32 值,需要先解引用,使 i 变为 mut i32 值,才能进行加减操作。
输出:
11
12
13
14
15