Vector 动态数组
本教程共 78 篇 · 第 36 篇 · 更新于 2026-08-08 · 约 11 分钟阅读
本节目标:学会用
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 处理,直接推出 v 是 Vec<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 == capacity,Vec 会申请一块更大的内存并把旧数据搬过去,这个”扩容”是有成本的。
好在 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 有很深的血缘关系。