rust 学习(12):常用集合(上)-- Vec

rust 学习(12):常用集合(上)-- Vec

文章目录

    • 一、回顾与导入
    • 二、创建 Vec
      • 2.1 使用 `Vec::new()`
      • 2.2 使用 `vec!` 宏(最常用)
      • 2.3 使用 `with_capacity()` 预分配容量
    • 三、读取元素
      • 3.1 索引语法 `[]`
      • 3.2 `get` 方法 —— 安全访问
      • 3.3 索引 vs get 对比
    • 四、修改 Vec
      • 4.1 添加元素:`push`
      • 4.2 删除元素:`pop`
      • 4.3 插入与删除
      • 4.4 修改指定位置
    • 五、遍历 Vec
      • 5.1 不可变遍历
      • 5.2 可变遍历
    • 六、常用方法
    • 七、所有权与借用
      • 7.1 存储非 Copy 类型
      • 7.2 引用类型存储
      • 7.3 遍历时不能修改
    • 八、完整代码示例
    • 九、常见陷阱与最佳实践
      • 陷阱1:索引越界
      • 陷阱2:遍历时修改
      • 陷阱3:引用失效
      • 性能最佳实践
    • 十、总结与速查
    • 十一、思考题
    • 参考链接

返回首页 | 上篇| 下篇

掌握 Rust 最常用的动态数组 —— 创建、增删改查、遍历与常用方法


一、回顾与导入

前几篇文章我们学习了 Rust 的核心概念:所有权、借用、结构体、枚举、模块系统。

现在开始学习 Rust 标准库提供的常用集合类型。这些类型存储在堆上,可以在运行时动态增长。

今天的主角是Vec<T>(Vector,动态数组),这是 Rust 中最常用的集合类型。

💡 与数组[T; N]不同,Vec的长度可以在运行时变化。


二、创建 Vec

2.1 使用Vec::new()

fnmain(){letv:Vec<i32>=Vec::new();// 需要显式标注类型println!("长度: {}, 容量: {}",v.len(),v.capacity());}

2.2 使用vec!宏(最常用)

fnmain(){letv=vec![1,2,3];// 类型自动推断为 Vec<i32>letv2=vec![0;5];// 创建包含 5 个 0 的 Vecletv3=vec!["hello","world"];println!("v: {:?}",v);// [1, 2, 3]println!("v2: {:?}",v2);// [0, 0, 0, 0, 0]}

2.3 使用with_capacity()预分配容量

fnmain(){// 预分配容量,避免多次重新分配letmutv:Vec<i32>=Vec::with_capacity(10);println!("长度: {}, 容量: {}",v.len(),v.capacity());foriin0..5{v.push(i);}println!("加入5个元素后 - 长度: {}, 容量: {}",v.len(),v.capacity());}

三、读取元素

3.1 索引语法[]

fnmain(){letv=vec![1,2,3,4,5];letfirst=v[0];letsecond=v[1];println!("第一个元素: {}, 第二个元素: {}",first,second);}

⚠️ 越界访问会 panic:

fnmain(){letv=vec![1,2,3];// let invalid = v[99]; // 💥 panic! index out of bounds}

3.2get方法 —— 安全访问

get返回Option<&T>,不会 panic:

fnmain(){letv=vec![1,2,3];matchv.get(2){Some(third)=>println!("第三个元素: {}",third),None=>println!("索引越界"),}matchv.get(99){Some(value)=>println!("{}",value),None=>println!("索引 99 越界"),// 走到这里}}

3.3 索引 vs get 对比

方式返回值越界行为适用场景
v[index]T(复制)或&Tpanic确定索引有效时
v.get(index)Option<&T>返回None不确定索引是否有效

四、修改 Vec

4.1 添加元素:push

fnmain(){letmutv=vec![1,2,3];v.push(4);v.push(5);println!("{:?}",v);// [1, 2, 3, 4, 5]}

4.2 删除元素:pop

fnmain(){letmutv=vec![1,2,3];letlast=v.pop();// 返回 Option<T>println!("弹出: {:?}, 剩余: {:?}",last,v);// Some(3), [1, 2]letnone=v.pop();// Some(2)letnone=v.pop();// Some(1)letnone=v.pop();// Noneprintln!("弹出空 Vec: {:?}",none);}

4.3 插入与删除

fnmain(){letmutv=vec![1,2,4,5];// 在索引 2 处插入 3v.insert(2,3);println!("插入后: {:?}",v);// [1, 2, 3, 4, 5]// 删除索引 1 处的元素letremoved=v.remove(1);println!("删除元素: {}, 结果: {:?}",removed,v);// 2, [1, 3, 4, 5]}

4.4 修改指定位置

fnmain(){letmutv=vec![1,2,3];v[0]=10;v[1]=20;v[2]=30;println!("{:?}",v);// [10, 20, 30]}

五、遍历 Vec

5.1 不可变遍历

fnmain(){letv=vec![1,2,3,4,5];// 方式1:直接遍历元素foritemin&v{println!("{}",item);}// 方式2:带索引遍历for(i,item)inv.iter().enumerate(){println!("v[{}] = {}",i,item);}}

5.2 可变遍历

fnmain(){letmutv=vec![1,2,3,4,5];// 遍历并修改每个元素foritemin&mutv{*item*=2;// 解引用后修改}println!("{:?}",v);// [2, 4, 6, 8, 10]}

六、常用方法

方法说明示例
len()返回长度v.len()
is_empty()是否为空v.is_empty()
capacity()返回容量v.capacity()
contains(&item)是否包含元素v.contains(&3)
first()/last()首/最后一个元素v.first()
clear()清空所有元素v.clear()
truncate(len)截断到指定长度v.truncate(3)
reserve(n)预分配容量v.reserve(10)
fnmain(){letmutv=vec![10,20,30,40,50];println!("长度: {}",v.len());// 5println!("是否为空: {}",v.is_empty());// falseprintln!("包含 30: {}",v.contains(&30));// trueifletSome(first)=v.first(){println!("第一个元素: {}",first);// 10}ifletSome(last)=v.last(){println!("最后一个元素: {}",last);// 50}v.truncate(3);println!("截断后: {:?}",v);// [10, 20, 30]v.clear();println!("清空后: {:?}, 长度: {}",v,v.len());// [], 0}

七、所有权与借用

7.1 存储非 Copy 类型

fnmain(){letmutv=Vec::new();lets1=String::from("hello");lets2=String::from("world");v.push(s1);// s1 的所有权移动到 vv.push(s2);// s2 的所有权移动到 v// println!("{}", s1); // ❌ 编译错误:s1 已移动println!("{:?}",v);// ["hello", "world"]}

7.2 引用类型存储

fnmain(){lets=String::from("hello");letv:Vec<&String>=vec![&s];// 存储引用println!("{}",s);// s 仍然可用println!("{}",v[0]);}

7.3 遍历时不能修改

fnmain(){letmutv=vec![1,2,3];// ❌ 错误:不能在遍历时修改 Vec// for item in &v {// v.push(4);// }// ✅ 正确:先收集,后修改letnew_items:Vec<i32>=(4..=6).collect();v.extend(new_items);println!("{:?}",v);}

八、完整代码示例

fnmain(){println!("=== 1. 创建 Vec ===");letv1=vec![1,2,3];letv2=vec![0;5];letv3=Vec::<i32>::new();letv4:Vec<i32>=(1..=5).collect();// 从迭代器创建println!("v1: {:?}",v1);println!("v2: {:?}",v2);println!("v4: {:?}",v4);println!("\n=== 2. 添加和删除 ===");letmutstack=Vec::new();stack.push(1);stack.push(2);stack.push(3);println!("栈: {:?}",stack);whileletSome(top)=stack.pop(){println!("弹出: {}",top);}println!("\n=== 3. 安全索引 ===");letv=vec![10,20,30];foriin0..=v.len(){matchv.get(i){Some(val)=>println!("v[{}] = {}",i,val),None=>println!("v[{}] 不存在",i),}}println!("\n=== 4. 遍历与修改 ===");letmutnumbers=vec![1,2,3,4,5];fornumin&mutnumbers{*num*=*num;// 平方}println!("平方后: {:?}",numbers);println!("\n=== 5. 常用方法 ===");letmutfruits=vec!["apple","banana","orange"];println!("是否有 banana: {}",fruits.contains(&"banana"));println!("第一个: {:?}",fruits.first());println!("最后一个: {:?}",fruits.last());fruits.insert(1,"grape");println!("插入后: {:?}",fruits);letremoved=fruits.remove(2);println!("删除 {} 后: {:?}",removed,fruits);println!("\n=== 6. 排序 ===");letmutnums=vec![5,2,8,1,9];nums.sort();println!("升序: {:?}",nums);nums.sort_by(|a,b|b.cmp(a));// 降序println!("降序: {:?}",nums);println!("\n=== 7. 过滤与映射 ===");letnums=vec![1,2,3,4,5,6,7,8,9,10];leteven:Vec<_>=nums.iter().filter(|&&x|x%2==0).collect();letdoubled:Vec<_>=nums.iter().map(|&x|x*2).collect();println!("偶数: {:?}",even);println!("加倍: {:?}",doubled);}

九、常见陷阱与最佳实践

陷阱1:索引越界

letv=vec![1,2,3];println!("{}",v[3]);// 💥 panic!

解决:使用get()进行安全检查

陷阱2:遍历时修改

letmutv=vec![1,2,3];foritemin&v{v.push(4);// ❌ 不能修改}

陷阱3:引用失效

letmutv=vec![1,2,3];letfirst=&v[0];// 获取引用v.push(4);// 可能触发重新分配println!("{}",first);// ❌ 风险:引用可能失效

性能最佳实践

场景推荐做法
已知大致元素数量使用Vec::with_capacity()预分配
频繁在头部插入/删除考虑VecDeque
需要去重考虑HashSet
需要快速查找考虑HashMap或二分查找

十、总结与速查

操作代码时间复杂度
创建空 VecVec::new()O(1)
创建带初值vec![1, 2, 3]O(n)
尾部添加v.push(x)均摊 O(1)
尾部删除v.pop()O(1)
索引访问v[i]O(1)
安全访问v.get(i)O(1)
插入v.insert(i, x)O(n)
删除v.remove(i)O(n)
遍历for x in &vO(n)
长度v.len()O(1)
清空v.clear()O(n)

核心要点

  • Vec是最常用的动态数组,长度可变
  • ✅ 使用vec![]宏创建最方便
  • ✅ 优先使用get()安全访问,避免 panic
  • ✅ 遍历时不能修改,需要修改可先收集再扩展
  • ✅ 预分配容量可以提升性能

十一、思考题

  1. 下面代码会输出什么?是否会 panic?
fnmain(){letv=vec![1,2,3];println!("{}",v.get(3).unwrap_or(&0));}
  1. Vec::new()Vec::with_capacity(10)有什么区别?

  2. 为什么在遍历 Vec 时不能修改它?即使不会导致重新分配也不行?


参考链接

  • Rust Book - Vectors
  • Rust Standard Library - Vec
  • Rust by Example - Vectors