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

文章目录

返回首页 | 上篇| 下篇


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


一、回顾与导入

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

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

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

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


二、创建 Vec

2.1 使用 Vec::new()

rust 复制代码
fn main() {
    let v: Vec<i32> = Vec::new();  // 需要显式标注类型
    println!("长度: {}, 容量: {}", v.len(), v.capacity());
}

2.2 使用 vec! 宏(最常用)

rust 复制代码
fn main() {
    let v = vec![1, 2, 3];  // 类型自动推断为 Vec<i32>
    let v2 = vec![0; 5];    // 创建包含 5 个 0 的 Vec
    let v3 = vec!["hello", "world"];

    println!("v: {:?}", v);    // [1, 2, 3]
    println!("v2: {:?}", v2);  // [0, 0, 0, 0, 0]
}

2.3 使用 with_capacity() 预分配容量

rust 复制代码
fn main() {
    // 预分配容量,避免多次重新分配
    let mut v: Vec<i32> = Vec::with_capacity(10);
    println!("长度: {}, 容量: {}", v.len(), v.capacity());

    for i in 0..5 {
        v.push(i);
    }
    println!("加入5个元素后 - 长度: {}, 容量: {}", v.len(), v.capacity());
}

三、读取元素

3.1 索引语法 []

rust 复制代码
fn main() {
    let v = vec![1, 2, 3, 4, 5];

    let first = v[0];
    let second = v[1];

    println!("第一个元素: {}, 第二个元素: {}", first, second);
}

⚠️ 越界访问会 panic:

rust 复制代码
fn main() {
    let v = vec![1, 2, 3];
    // let invalid = v[99];  // 💥 panic! index out of bounds
}

3.2 get 方法 ------ 安全访问

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

rust 复制代码
fn main() {
    let v = vec![1, 2, 3];

    match v.get(2) {
        Some(third) => println!("第三个元素: {}", third),
        None => println!("索引越界"),
    }

    match v.get(99) {
        Some(value) => println!("{}", value),
        None => println!("索引 99 越界"),  // 走到这里
    }
}

3.3 索引 vs get 对比

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

四、修改 Vec

4.1 添加元素:push

rust 复制代码
fn main() {
    let mut v = vec![1, 2, 3];
    v.push(4);
    v.push(5);
    println!("{:?}", v);  // [1, 2, 3, 4, 5]
}

4.2 删除元素:pop

rust 复制代码
fn main() {
    let mut v = vec![1, 2, 3];

    let last = v.pop();  // 返回 Option<T>
    println!("弹出: {:?}, 剩余: {:?}", last, v);  // Some(3), [1, 2]

    let none = v.pop();  // Some(2)
    let none = v.pop();  // Some(1)
    let none = v.pop();  // None
    println!("弹出空 Vec: {:?}", none);
}

4.3 插入与删除

rust 复制代码
fn main() {
    let mut v = vec![1, 2, 4, 5];

    // 在索引 2 处插入 3
    v.insert(2, 3);
    println!("插入后: {:?}", v);  // [1, 2, 3, 4, 5]

    // 删除索引 1 处的元素
    let removed = v.remove(1);
    println!("删除元素: {}, 结果: {:?}", removed, v);  // 2, [1, 3, 4, 5]
}

4.4 修改指定位置

rust 复制代码
fn main() {
    let mut v = vec![1, 2, 3];

    v[0] = 10;
    v[1] = 20;
    v[2] = 30;

    println!("{:?}", v);  // [10, 20, 30]
}

五、遍历 Vec

5.1 不可变遍历

rust 复制代码
fn main() {
    let v = vec![1, 2, 3, 4, 5];

    // 方式1:直接遍历元素
    for item in &v {
        println!("{}", item);
    }

    // 方式2:带索引遍历
    for (i, item) in v.iter().enumerate() {
        println!("v[{}] = {}", i, item);
    }
}

5.2 可变遍历

rust 复制代码
fn main() {
    let mut v = vec![1, 2, 3, 4, 5];

    // 遍历并修改每个元素
    for item in &mut v {
        *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)
rust 复制代码
fn main() {
    let mut v = vec![10, 20, 30, 40, 50];

    println!("长度: {}", v.len());           // 5
    println!("是否为空: {}", v.is_empty());   // false
    println!("包含 30: {}", v.contains(&30)); // true

    if let Some(first) = v.first() {
        println!("第一个元素: {}", first);    // 10
    }

    if let Some(last) = v.last() {
        println!("最后一个元素: {}", last);   // 50
    }

    v.truncate(3);
    println!("截断后: {:?}", v);  // [10, 20, 30]

    v.clear();
    println!("清空后: {:?}, 长度: {}", v, v.len());  // [], 0
}

七、所有权与借用

7.1 存储非 Copy 类型

rust 复制代码
fn main() {
    let mut v = Vec::new();

    let s1 = String::from("hello");
    let s2 = String::from("world");

    v.push(s1);   // s1 的所有权移动到 v
    v.push(s2);   // s2 的所有权移动到 v

    // println!("{}", s1);  // ❌ 编译错误:s1 已移动

    println!("{:?}", v);  // ["hello", "world"]
}

7.2 引用类型存储

rust 复制代码
fn main() {
    let s = String::from("hello");
    let v: Vec<&String> = vec![&s];  // 存储引用

    println!("{}", s);  // s 仍然可用
    println!("{}", v[0]);
}

7.3 遍历时不能修改

rust 复制代码
fn main() {
    let mut v = vec![1, 2, 3];

    // ❌ 错误:不能在遍历时修改 Vec
    // for item in &v {
    //     v.push(4);
    // }

    // ✅ 正确:先收集,后修改
    let new_items: Vec<i32> = (4..=6).collect();
    v.extend(new_items);
    println!("{:?}", v);
}

八、完整代码示例

rust 复制代码
fn main() {
    println!("=== 1. 创建 Vec ===");
    let v1 = vec![1, 2, 3];
    let v2 = vec![0; 5];
    let v3 = Vec::<i32>::new();
    let v4: Vec<i32> = (1..=5).collect();  // 从迭代器创建

    println!("v1: {:?}", v1);
    println!("v2: {:?}", v2);
    println!("v4: {:?}", v4);

    println!("\n=== 2. 添加和删除 ===");
    let mut stack = Vec::new();
    stack.push(1);
    stack.push(2);
    stack.push(3);
    println!("栈: {:?}", stack);

    while let Some(top) = stack.pop() {
        println!("弹出: {}", top);
    }

    println!("\n=== 3. 安全索引 ===");
    let v = vec![10, 20, 30];

    for i in 0..=v.len() {
        match v.get(i) {
            Some(val) => println!("v[{}] = {}", i, val),
            None => println!("v[{}] 不存在", i),
        }
    }

    println!("\n=== 4. 遍历与修改 ===");
    let mut numbers = vec![1, 2, 3, 4, 5];

    for num in &mut numbers {
        *num *= *num;  // 平方
    }
    println!("平方后: {:?}", numbers);

    println!("\n=== 5. 常用方法 ===");
    let mut fruits = vec!["apple", "banana", "orange"];
    println!("是否有 banana: {}", fruits.contains(&"banana"));
    println!("第一个: {:?}", fruits.first());
    println!("最后一个: {:?}", fruits.last());

    fruits.insert(1, "grape");
    println!("插入后: {:?}", fruits);

    let removed = fruits.remove(2);
    println!("删除 {} 后: {:?}", removed, fruits);

    println!("\n=== 6. 排序 ===");
    let mut nums = vec![5, 2, 8, 1, 9];
    nums.sort();
    println!("升序: {:?}", nums);

    nums.sort_by(|a, b| b.cmp(a));  // 降序
    println!("降序: {:?}", nums);

    println!("\n=== 7. 过滤与映射 ===");
    let nums = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
    let even: Vec<_> = nums.iter().filter(|&&x| x % 2 == 0).collect();
    let doubled: Vec<_> = nums.iter().map(|&x| x * 2).collect();

    println!("偶数: {:?}", even);
    println!("加倍: {:?}", doubled);
}

九、常见陷阱与最佳实践

陷阱1:索引越界

rust 复制代码
let v = vec![1, 2, 3];
println!("{}", v[3]);  // 💥 panic!

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

陷阱2:遍历时修改

rust 复制代码
let mut v = vec![1, 2, 3];
for item in &v {
    v.push(4);  // ❌ 不能修改
}

陷阱3:引用失效

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

性能最佳实践

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

十、总结与速查

操作 代码 时间复杂度
创建空 Vec Vec::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 &v O(n)
长度 v.len() O(1)
清空 v.clear() O(n)

核心要点

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

十一、思考题

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

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


参考链接

相关推荐
落知秋19 小时前
RUST中的trait是什么?
笔记·rust
Embedded-Xin1 天前
中间件—zenoh零基础入门
linux·中间件·rust·机器人·自动驾驶·嵌入式
程序员爱钓鱼1 天前
Rust 可变借用详解:&mut T 与安全修改数据
后端·面试·rust
SomeB1oody2 天前
【RustyML入门】2.9. MeanShift
开发语言·后端·机器学习·rust·教程
SomeB1oody2 天前
【RustyML入门】2.10. 主成分分析
开发语言·后端·机器学习·rust·教程
yushikong2 天前
关于rust开发ch32x033f8p6的一些记录
开发语言·后端·rust
程序员爱钓鱼2 天前
Rust Borrow借用详解:不转移所有权访问数据
后端·面试·rust
程序员爱钓鱼3 天前
Rust Copy详解:隐式复制与轻量数据类型
前端·后端·rust