首页 / Rust 入门教程 / Vector 动态数组

Rust 入门教程

Vector 动态数组

本教程共 78 篇 · 第 36 篇 · 更新于 2026-08-08 · 约 11 分钟阅读

RustRust 入门教程Vec动态数组集合getpush排序

本节目标:学会用 Vec<T> 存一组同类型数据,掌握增删改查和遍历,理解为什么同时借用元素和 push 会报错,并会给它排序。

前面我们用过好几次 vec!,但一直没细讲。它背后的类型是 Vec<T>,中文叫”动态数组”或”向量”。当你想管理一串同类型的数据——一组分数、一列用户名——它就是首选。

1-1 Vec 是什么

Vec<T> 把多个值挨个排在内存里(连续存储),所以按下标取某个元素非常快。它最大的特点是”动态”:数组能随时增长或缩短,不用像固定数组那样一开始就定死长度。

它有个硬规定:所有元素必须是同一类型 T。想存不同类型怎么办?后面会讲用枚举或 trait 对象来绕开,这是后话。

Note

连续存储带来一个好处:CPU 缓存更友好,遍历快。但代价是中间插入/删除要搬移后面所有元素,所以 push(尾部追加)最快,中间位置插入偏慢。

1-2 创建 Vec 的几种方式

最常见的是 Vec::new(),创建空数组:

let v: Vec<i32> = Vec::new();

这里必须显式标类型 Vec<i32>,因为空数组里没有任何元素能帮编译器推断类型。一旦你往里塞一个值,类型就有着落了:

let mut v = Vec::new();
v.push(1); // 编译器由此推断 v 是 Vec<i32>

更省事的是 vec! 宏,创建时直接给初值:

let v = vec![1, 2, 3];

编译器看里面是整数,默认按 i32 处理,直接推出 vVec<i32>。日常写代码基本都用 vec!。它还有两种常见变体:

let v = vec![0; 5];          // 五个 0:[0, 0, 0, 0, 0]
let v_from = Vec::from([1, 2, 3]); // 从数组转换
Tip

如果你提前知道要装多少个元素,用 Vec::with_capacity(n) 一次性预留空间,能少做好几次内存搬移,大数组时性能更好。

1-3 增和删

往尾巴加元素用 push,记得把变量声明成 mut

let mut v = Vec::new();
v.push(1);
v.push(2);

删除尾巴元素用 pop,它返回 Option<T>——有就 Some(值),空了就 None

let mut v = vec![1, 2, 3];
let last = v.pop(); // Some(3)

还有 insert(位置, 值) 在指定位置插入、remove(位置) 删除指定位置并返回该值、clear() 清空。另外 append 把另一个 Vec 整个接在自己尾巴上(被接的那个会清空),extend 则追加一个可迭代的东西:

let mut v = vec![1, 2];
let mut v1 = vec![3, 4];
v.append(&mut v1);      // v: [1,2,3,4],v1 变空
v.extend([5, 6]);       // v: [1,2,3,4,5,6]

Vec 也遵守所有权规则:离开作用域时,v 和它里面的所有元素会一起被释放掉。

1-4 读取元素:下标还是 get

取第几个元素有两种写法,下标从 0 开始:

let v = vec![1, 2, 3, 4, 5];

let third: &i32 = &v[2];
println!("第三个元素是 {third}");

match v.get(2) {
    Some(third) => println!("第三个元素是 {third}"),
    None => println!("没有第三个元素"),
}

两者区别在越界时表现完全不同:

  • &v[100]:下标越界,程序直接 panic 崩溃;
  • v.get(100):返回 None,不会崩,让你自己处理”没有”的情况。
Note

怎么选?当你确信索引合法(比如刚算出来、肯定在范围内),用下标更简洁。索引来自外部输入、可能越界时,用 get 更安全——你永远猜不准用户会输什么数字进来。

1-5 一个容易踩的借用坑

下面这段看似无害,却编不过:

let mut v = vec![1, 2, 3, 4, 5];

let first = &v[0]; // 不可变借用第一个元素

v.push(6);         // 可变借用,往里加元素

println!("第一个元素是 {first}");

报错说”不能对 v 做可变借用,因为它已被不可变借用”。按理说,一个是读第 0 个、一个是在尾巴追加,互不干扰啊?

问题出在 Vec 是动态数组。当容量不够时,Rust 会重新申请一块更大的内存,把旧元素整个搬过去。那么 first 这个引用原本指向旧内存,搬完就悬空了——指向一块已经无效的内存。编译器宁可在编译期拦住你,也不让你埋下这种隐患。

Warning

这是初学者常卡住的地方。解决办法很简单:把 push 放在使用 first 之前,或者干脆别同时持有两个借用。记住——只要还拿着 Vec 里元素的引用,就别对它做会改变长度的操作。

1-6 遍历元素

挨个访问元素,优先用 for 循环遍历,比下标循环更安全也更快(省去每次的边界检查):

let v = vec![1, 2, 3];
for i in &v {
    println!("{i}");
}

想一边遍历一边改,用 &mut

let mut v = vec![1, 2, 3];
for i in &mut v {
    *i += 10; // 解引用后才能修改
}

如果要”遍历的同时往里加”,直接 push 会触发上面那个借用冲突。正确做法:先把要加的元素收集到另一个 Vec,循环结束后再 extend 进去。

1-7 想存不同类型怎么办

Vec 只认同一类型,但有时你确实需要装不同东西。第一种办法是用枚举,把不同类型统一成同一个枚举类型:

#[derive(Debug)]
enum IpAddr {
    V4(String),
    V6(String),
}

fn main() {
    let v = vec![
        IpAddr::V4("127.0.0.1".to_string()),
        IpAddr::V6("::1".to_string()),
    ];
    for ip in v {
        println!("{:?}", ip);
    }
}

两种 IP 地址类型不同,但都属于 IpAddr 枚举的成员,于是能装进同一个 Vec<IpAddr>

第二种办法是 trait 对象 Vec<Box<dyn Trait>>,更灵活,适合类型在运行时才能确定的场景:

trait Speaker {
    fn speak(&self);
}

struct Dog;
impl Speaker for Dog {
    fn speak(&self) { println!("汪汪"); }
}

struct Cat;
impl Speaker for Cat {
    fn speak(&self) { println!("喵喵"); }
}

fn main() {
    let pets: Vec<Box<dyn Speaker>> = vec![Box::new(Dog), Box::new(Cat)];
    for p in pets {
        p.speak();
    }
}
Note

实际项目里 trait 对象数组比枚举数组更常见,因为它不受枚举变体数量的限制,运行时还能动态增减类型。这一章先记住”有这两条路”即可,trait 对象留到讲 trait 时再展开。

1-8 容量与性能:len、capacity 与扩容

Vec 有两个容易混淆的概念:**长度(len)**是当前存了多少元素,**容量(capacity)**是不用重新申请内存就能容纳多少元素。push 时如果 len == capacityVec 会申请一块更大的内存并把旧数据搬过去,这个”扩容”是有成本的。

好在 push 的扩容是按倍数增长的(通常是翻倍),所以平均下来每次 push 的成本被摊薄,是”均摊 O(1)“。如果你提前知道大概要放多少元素,可以用 Vec::with_capacity(n) 或中途调用 reserve(n) 预留空间,避免反复扩容;反之想释放多余内存可用 shrink_to_fit(),但日常很少需要。

let mut v: Vec<i32> = Vec::with_capacity(100);
for i in 0..100 {
    v.push(i); // 不会触发任何扩容
}
println!("len={}, capacity={}", v.len(), v.capacity());
Tip

不要为了”性能”盲目调用 with_capacity,只有当你确实知道规模、且这个 Vec 会被频繁 push 时才值得。过度预留反而浪费内存。

1-9 给 Vec 排序

排序是高频需求。Vec 提供稳定排序 sort 和非稳定排序 sort_unstable(后者通常更快,但不保证相等元素的相对顺序)。整数数组直接排:

let mut v = vec![1, 5, 10, 2, 15];
v.sort_unstable();
assert_eq!(v, vec![1, 2, 5, 10, 15]);

但给浮点数排序会踩坑:f32/f64 里有个 NAN(不是数字),它无法和任何值比较大小,所以浮点类型只实现了 PartialOrd 而非 Ord,直接 sort_unstable() 会报错。你需要自己给比较依据:

let mut v = vec![1.0, 5.6, 10.3, 2.0, 15.0];
v.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap());

对自定义结构体排序,则用 sort_unstable_by 指定按哪个字段比:

#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}

fn main() {
    let mut people = vec![
        Person { name: "Zoe".into(), age: 25 },
        Person { name: "Al".into(), age: 60 },
    ];
    people.sort_unstable_by(|a, b| b.age.cmp(&a.age)); // 按年龄倒序
    println!("{people:?}");
}
Warning

NAN 是个经典陷阱:只要数组里混进一个 NAN,基于 Ord 的排序就编译不过。处理外部数据时务必留意浮点合法性,或改用 partial_cmp

1-10 常用方法一览

几个高频方法先混个脸熟:

let mut v = vec![1, 2];
assert!(!v.is_empty());          // 是否空
v.insert(2, 3);                  // 在索引2插入,v: [1,2,3]
assert_eq!(v.remove(1), 2);      // 删索引1返回2,v: [1,3]
v.retain(|&x| x > 1);            // 只保留满足条件的,v: [3]
v.clear();                       // 清空

let v = vec![11, 22, 33, 44, 55];
let slice = &v[1..=3];           // 切片,拿到 [22,33,44]
assert_eq!(slice, &[22, 33, 44]);

想看完整清单,查标准库 Vec 文档即可。这一章把”创建、增删、读取、遍历、存异类、排序”这几块吃透,日常够用了。

1-11 小结

  • Vec<T> 是内存连续、可动态增长的同类型集合;
  • vec!Vec::new() 创建,push/pop/insert/remove 增删;
  • 读取用下标(越界 panic)或 get(越界返回 None);
  • 拿着元素引用时别做改变长度的操作,否则借用冲突;
  • 遍历用 for,改值用 &mut
  • 排序用 sort/sort_unstable,浮点注意 NAN
  • 不同类型可借枚举或 trait 对象统一。

下一章看字符串 String,它和 Vec 有很深的血缘关系。