向量

数组的优势也恰恰是其局限所在:其大小必须在编译时预先确定。如果你尝试创建一个大小仅在运行时才知道的数组,将会遇到编译错误:

#![allow(unused)]
fn main() {
let n = 10;
let numbers: [u32; n];
}
error[E0435]: 尝试在常量中使用非常量值
 --> src/main.rs:3:20
  |
2 | let n = 10;
3 | let numbers: [u32; n];
  |                    ^ 非常量值

对于票务管理系统来说,数组并不适用——我们在编译时不知道需要存储多少张票。这时候,Vec就派上用场了。

Vec

Vec是标准库提供的一个可增长的数组类型。
你可以使用Vec::new函数创建一个空的向量:

#![allow(unused)]
fn main() {
let mut numbers: Vec<u32> = Vec::new();
}

然后,你可以使用push方法向向量中添加元素:

#![allow(unused)]
fn main() {
numbers.push(1);
numbers.push(2);
numbers.push(3);
}

新值会被追加到向量的末尾。
如果在创建时就知道元素值,也可以使用vec!宏来创建一个初始化的向量:

#![allow(unused)]
fn main() {
let numbers = vec![1, 2, 3];
}

访问元素

访问元素的语法与数组相同:

#![allow(unused)]
fn main() {
let numbers = vec![1, 2, 3];
let first = numbers[0];
let second = numbers[1];
let third = numbers[2];
}

索引必须是usize类型。
同样,你也可以使用get方法,它返回一个Option<&T>

#![allow(unused)]
fn main() {
let numbers = vec![1, 2, 3];
assert_eq!(numbers.get(0), Some(&1));
// 如果尝试访问越界索引,会得到 `None` 而不是恐慌。
assert_eq!(numbers.get(3), None);
}

访问同样进行了边界检查,复杂度为O(1)。

内存布局

Vec是一个堆分配的数据结构。
当你创建一个Vec时,它会在堆上分配内存来存储元素。

如果运行以下代码:

#![allow(unused)]
fn main() {
let mut numbers = Vec::with_capacity(3);
numbers.push(1);
numbers.push(2);
}

得到的内存布局如下:

      +---------+--------+----------+
栈    | 指针 | 长度 | 容量 | 
      |  |      |   2    |    3     |
      +--|------+--------+----------+
         |
         |
         v
       +---+---+---+
堆:   | 1 | 2 | ? |
       +---+---+---+

Vec跟踪三件事:

  • 指针到你预留的堆区域。
  • 长度,即向量中有多少个元素。
  • 容量,即堆上预留空间能容纳的元素数量。

这个布局应该很眼熟:它和String完全一样!这不是巧合:String本质上在内部定义为字节的向量,即Vec<u8>

#![allow(unused)]
fn main() {
pub struct String {
    vec: Vec<u8>,
}
}