rust 学习(12):常用集合(上)-- Vec
文章目录一、回顾与导入二、创建 Vec2.1 使用 Vec::new()2.2 使用 vec! 宏最常用2.3 使用 with_capacity() 预分配容量三、读取元素3.1 索引语法 []3.2 get 方法 —— 安全访问3.3 索引 vs get 对比四、修改 Vec4.1 添加元素push4.2 删除元素pop4.3 插入与删除4.4 修改指定位置五、遍历 Vec5.1 不可变遍历5.2 可变遍历六、常用方法七、所有权与借用7.1 存储非 Copy 类型7.2 引用类型存储7.3 遍历时不能修改八、完整代码示例九、常见陷阱与最佳实践陷阱1索引越界陷阱2遍历时修改陷阱3引用失效性能最佳实践十、总结与速查十一、思考题参考链接返回首页 | 上篇| 下篇掌握 Rust 最常用的动态数组 —— 创建、增删改查、遍历与常用方法一、回顾与导入前几篇文章我们学习了 Rust 的核心概念所有权、借用、结构体、枚举、模块系统。现在开始学习 Rust 标准库提供的常用集合类型。这些类型存储在堆上可以在运行时动态增长。今天的主角是VecTVector动态数组这是 Rust 中最常用的集合类型。 与数组[T; N]不同Vec的长度可以在运行时变化。二、创建 Vec2.1 使用Vec::new()fnmain(){letv:Veci32Vec::new();// 需要显式标注类型println!(长度: {}, 容量: {},v.len(),v.capacity());}2.2 使用vec!宏最常用fnmain(){letvvec![1,2,3];// 类型自动推断为 Veci32letv2vec![0;5];// 创建包含 5 个 0 的 Vecletv3vec![hello,world];println!(v: {:?},v);// [1, 2, 3]println!(v2: {:?},v2);// [0, 0, 0, 0, 0]}2.3 使用with_capacity()预分配容量fnmain(){// 预分配容量避免多次重新分配letmutv:Veci32Vec::with_capacity(10);println!(长度: {}, 容量: {},v.len(),v.capacity());foriin0..5{v.push(i);}println!(加入5个元素后 - 长度: {}, 容量: {},v.len(),v.capacity());}三、读取元素3.1 索引语法[]fnmain(){letvvec![1,2,3,4,5];letfirstv[0];letsecondv[1];println!(第一个元素: {}, 第二个元素: {},first,second);}⚠️ 越界访问会 panicfnmain(){letvvec![1,2,3];// let invalid v[99]; // panic! index out of bounds}3.2get方法 —— 安全访问get返回OptionT不会 panicfnmain(){letvvec![1,2,3];matchv.get(2){Some(third)println!(第三个元素: {},third),Noneprintln!(索引越界),}matchv.get(99){Some(value)println!({},value),Noneprintln!(索引 99 越界),// 走到这里}}3.3 索引 vs get 对比方式返回值越界行为适用场景v[index]T复制或Tpanic确定索引有效时v.get(index)OptionT返回None不确定索引是否有效四、修改 Vec4.1 添加元素pushfnmain(){letmutvvec![1,2,3];v.push(4);v.push(5);println!({:?},v);// [1, 2, 3, 4, 5]}4.2 删除元素popfnmain(){letmutvvec![1,2,3];letlastv.pop();// 返回 OptionTprintln!(弹出: {:?}, 剩余: {:?},last,v);// Some(3), [1, 2]letnonev.pop();// Some(2)letnonev.pop();// Some(1)letnonev.pop();// Noneprintln!(弹出空 Vec: {:?},none);}4.3 插入与删除fnmain(){letmutvvec![1,2,4,5];// 在索引 2 处插入 3v.insert(2,3);println!(插入后: {:?},v);// [1, 2, 3, 4, 5]// 删除索引 1 处的元素letremovedv.remove(1);println!(删除元素: {}, 结果: {:?},removed,v);// 2, [1, 3, 4, 5]}4.4 修改指定位置fnmain(){letmutvvec![1,2,3];v[0]10;v[1]20;v[2]30;println!({:?},v);// [10, 20, 30]}五、遍历 Vec5.1 不可变遍历fnmain(){letvvec![1,2,3,4,5];// 方式1直接遍历元素foriteminv{println!({},item);}// 方式2带索引遍历for(i,item)inv.iter().enumerate(){println!(v[{}] {},i,item);}}5.2 可变遍历fnmain(){letmutvvec![1,2,3,4,5];// 遍历并修改每个元素foriteminmutv{*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(){letmutvvec![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(){letmutvVec::new();lets1String::from(hello);lets2String::from(world);v.push(s1);// s1 的所有权移动到 vv.push(s2);// s2 的所有权移动到 v// println!({}, s1); // ❌ 编译错误s1 已移动println!({:?},v);// [hello, world]}7.2 引用类型存储fnmain(){letsString::from(hello);letv:VecStringvec![s];// 存储引用println!({},s);// s 仍然可用println!({},v[0]);}7.3 遍历时不能修改fnmain(){letmutvvec![1,2,3];// ❌ 错误不能在遍历时修改 Vec// for item in v {// v.push(4);// }// ✅ 正确先收集后修改letnew_items:Veci32(4..6).collect();v.extend(new_items);println!({:?},v);}八、完整代码示例fnmain(){println!( 1. 创建 Vec );letv1vec![1,2,3];letv2vec![0;5];letv3Vec::i32::new();letv4:Veci32(1..5).collect();// 从迭代器创建println!(v1: {:?},v1);println!(v2: {:?},v2);println!(v4: {:?},v4);println!(\n 2. 添加和删除 );letmutstackVec::new();stack.push(1);stack.push(2);stack.push(3);println!(栈: {:?},stack);whileletSome(top)stack.pop(){println!(弹出: {},top);}println!(\n 3. 安全索引 );letvvec![10,20,30];foriin0..v.len(){matchv.get(i){Some(val)println!(v[{}] {},i,val),Noneprintln!(v[{}] 不存在,i),}}println!(\n 4. 遍历与修改 );letmutnumbersvec![1,2,3,4,5];fornuminmutnumbers{*num**num;// 平方}println!(平方后: {:?},numbers);println!(\n 5. 常用方法 );letmutfruitsvec![apple,banana,orange];println!(是否有 banana: {},fruits.contains(banana));println!(第一个: {:?},fruits.first());println!(最后一个: {:?},fruits.last());fruits.insert(1,grape);println!(插入后: {:?},fruits);letremovedfruits.remove(2);println!(删除 {} 后: {:?},removed,fruits);println!(\n 6. 排序 );letmutnumsvec![5,2,8,1,9];nums.sort();println!(升序: {:?},nums);nums.sort_by(|a,b|b.cmp(a));// 降序println!(降序: {:?},nums);println!(\n 7. 过滤与映射 );letnumsvec![1,2,3,4,5,6,7,8,9,10];leteven:Vec_nums.iter().filter(|x|x%20).collect();letdoubled:Vec_nums.iter().map(|x|x*2).collect();println!(偶数: {:?},even);println!(加倍: {:?},doubled);}九、常见陷阱与最佳实践陷阱1索引越界letvvec![1,2,3];println!({},v[3]);// panic!✅解决使用get()进行安全检查陷阱2遍历时修改letmutvvec![1,2,3];foriteminv{v.push(4);// ❌ 不能修改}陷阱3引用失效letmutvvec![1,2,3];letfirstv[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✅ 遍历时不能修改需要修改可先收集再扩展✅ 预分配容量可以提升性能十一、思考题下面代码会输出什么是否会 panicfnmain(){letvvec![1,2,3];println!({},v.get(3).unwrap_or(0));}Vec::new()和Vec::with_capacity(10)有什么区别为什么在遍历 Vec 时不能修改它即使不会导致重新分配也不行参考链接Rust Book - VectorsRust Standard Library - VecRust by Example - Vectors