Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 声明了一个元素类型为 i32Vector(常用)。
  • 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]; 这里的 firstv 的不可变引用,两者又在同一个作用域下,所以会报错。
  • 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