Rust Vec 动态数组详解:创建、增删、遍历与排序

《Rust 编程实战》系列第 19 篇

在上一篇文章中,我们学习了 String&str

这一篇继续学习 Rust 中最常用的集合类型之一:

r 复制代码
Vec<T>

Vec 通常称为动态数组,也叫向量。

与普通数组不同,Vec 的长度可以在程序运行过程中变化。我们可以动态添加、删除和修改元素。

例如:

ini 复制代码
let mut numbers = vec![10, 20, 30];

numbers.push(40);

执行后,numbers 变成:

csharp 复制代码
[10, 20, 30, 40]

本文将介绍:

  • 什么是 Vec

  • 创建动态数组

  • 添加和删除元素

  • 访问和修改元素

  • 遍历 Vec

  • len()capacity()

  • 排序、查找和去重

  • Vec<String> 的所有权

  • Vec、数组和切片的区别

  • 常见错误

  • 实战案例


什么是 Vec?

Vec<T> 是 Rust 标准库提供的可增长集合。

它具有以下特点:

  • 所有元素必须是相同类型

  • 元素按照插入顺序排列

  • 长度可以动态变化

  • 数据通常存储在堆上

  • 支持添加、删除、排序和查找

例如:

rust 复制代码
fn main() {
    let numbers = vec![10, 20, 30];

    println!("{:?}", numbers);
}

输出:

csharp 复制代码
[10, 20, 30]

这里 numbers 的类型通常是:

rust 复制代码
Vec<i32>

使用 vec! 宏创建 Vec

最常用的创建方式是:

css 复制代码
vec![]

例如:

rust 复制代码
fn main() {
    let fruits = vec!["苹果", "香蕉", "橙子"];

    println!("{:?}", fruits);
}

也可以显式声明类型:

rust 复制代码
fn main() {
    let scores: Vec<i32> = vec![80, 90, 100];

    println!("{:?}", scores);
}

Rust 通常可以根据元素自动推断类型。


创建空 Vec

可以使用:

arduino 复制代码
Vec::new()

例如:

rust 复制代码
fn main() {
    let numbers: Vec<i32> = Vec::new();

    println!("{:?}", numbers);
}

创建空 Vec 时,通常必须指定元素类型,因为编译器无法从空集合推断。

也可以写成:

ruby 复制代码
let numbers = Vec::<i32>::new();

使用 Vec::with_capacity()

如果提前知道大概会保存多少元素,可以预先分配容量:

rust 复制代码
fn main() {
    let numbers: Vec<i32> =
        Vec::with_capacity(100);

    println!("长度:{}", numbers.len());
    println!("容量:{}", numbers.capacity());
}

输出类似:

复制代码
长度:0
容量:100

这不会创建 100 个元素,只会提前准备容纳 100 个元素的空间。


使用重复值创建 Vec

可以使用:

css 复制代码
vec![值; 数量]

例如:

rust 复制代码
fn main() {
    let numbers = vec![0; 5];

    println!("{:?}", numbers);
}

输出:

csharp 复制代码
[0, 0, 0, 0, 0]

这种写法适合初始化状态、计数器或缓冲区。


使用 push() 添加元素

Vec 默认不可变。

如果需要添加元素,必须使用 mut

ini 复制代码
fn main() {
    let mut numbers = vec![10, 20];

    numbers.push(30);
    numbers.push(40);

    println!("{:?}", numbers);
}

输出:

csharp 复制代码
[10, 20, 30, 40]

push() 会把元素添加到末尾。

新元素类型必须与原有元素类型一致。


使用 pop() 删除末尾元素

pop() 会删除并返回最后一个元素。

ini 复制代码
fn main() {
    let mut numbers = vec![10, 20, 30];

    let value = numbers.pop();

    println!("删除结果:{:?}", value);
    println!("剩余数据:{:?}", numbers);
}

输出:

scss 复制代码
删除结果:Some(30)
剩余数据:[10, 20]

pop() 返回:

r 复制代码
Option<T>

因为空 Vec 没有元素可以删除。

javascript 复制代码
if let Some(value) = numbers.pop() {
    println!("删除了:{}", value);
}

使用 insert() 插入元素

可以在指定索引位置插入元素:

ini 复制代码
fn main() {
    let mut numbers = vec![10, 30, 40];

    numbers.insert(1, 20);

    println!("{:?}", numbers);
}

输出:

csharp 复制代码
[10, 20, 30, 40]

原来索引 1 及其后面的元素会向后移动。

如果插入位置超过长度,程序会 panic。


使用 remove() 删除指定元素

rust 复制代码
fn main() {
    let mut fruits =
        vec!["苹果", "香蕉", "橙子"];

    let removed = fruits.remove(1);

    println!("删除:{}", removed);
    println!("剩余:{:?}", fruits);
}

输出:

css 复制代码
删除:香蕉
剩余:["苹果", "橙子"]

remove() 会移动后面的元素,因此频繁从开头或中间删除可能成本较高。


使用 swap_remove() 快速删除

如果不关心元素顺序,可以使用:

scss 复制代码
swap_remove()
ini 复制代码
fn main() {
    let mut numbers = vec![10, 20, 30, 40];

    let removed = numbers.swap_remove(1);

    println!("删除:{}", removed);
    println!("剩余:{:?}", numbers);
}

输出可能是:

css 复制代码
删除:20
剩余:[10, 40, 30]

它会把最后一个元素移动到删除位置,因此速度更快,但会改变顺序。


访问 Vec 元素

可以使用索引访问:

ini 复制代码
fn main() {
    let numbers = vec![10, 20, 30];

    println!("{}", numbers[0]);
    println!("{}", numbers[1]);
}

索引从 0 开始。

如果访问越界索引:

arduino 复制代码
println!("{}", numbers[10]);

程序会 panic。


使用 get() 安全访问

当索引不确定时,推荐使用:

scss 复制代码
get()
ini 复制代码
fn main() {
    let numbers = vec![10, 20, 30];
    let index = 5;

    match numbers.get(index) {
        Some(value) => {
            println!("元素:{}", value);
        }
        None => {
            println!("索引超出范围");
        }
    }
}

get() 返回:

r 复制代码
Option<&T>

因此不会因为越界直接崩溃。


获取第一个和最后一个元素

rust 复制代码
fn main() {
    let numbers = vec![10, 20, 30];

    println!("{:?}", numbers.first());
    println!("{:?}", numbers.last());
}

输出:

scss 复制代码
Some(10)
Some(30)

两者都返回 Option<&T>,因为集合可能为空。


修改 Vec 元素

ini 复制代码
fn main() {
    let mut numbers = vec![10, 20, 30];

    numbers[1] = 200;

    println!("{:?}", numbers);
}

输出:

csharp 复制代码
[10, 200, 30]

也可以使用:

scss 复制代码
get_mut()

安全获得可变引用:

ini 复制代码
fn main() {
    let mut numbers = vec![10, 20, 30];

    if let Some(value) = numbers.get_mut(1) {
        *value = 200;
    }

    println!("{:?}", numbers);
}

遍历 Vec

最常见的方式是使用 for

rust 复制代码
fn main() {
    let numbers = vec![10, 20, 30];

    for number in &numbers {
        println!("{}", number);
    }

    println!("{:?}", numbers);
}

使用:

yaml 复制代码
&numbers

可以借用集合,遍历后原 Vec 仍然可以使用。


遍历并修改元素

使用可变引用:

rust 复制代码
fn main() {
    let mut numbers = vec![10, 20, 30];

    for number in &mut numbers {
        *number *= 2;
    }

    println!("{:?}", numbers);
}

输出:

csharp 复制代码
[20, 40, 60]

循环变量是可变引用,因此需要使用:

typescript 复制代码
*number

进行解引用。


获取索引和值

可以使用:

scss 复制代码
enumerate()
javascript 复制代码
fn main() {
    let fruits =
        vec!["苹果", "香蕉", "橙子"];

    for (index, fruit) in
        fruits.iter().enumerate()
    {
        println!("{}:{}", index, fruit);
    }
}

输出:

复制代码
0:苹果
1:香蕉
2:橙子

len() 与 is_empty()

获取当前元素数量:

go 复制代码
numbers.len()

判断是否为空:

scss 复制代码
numbers.is_empty()

示例:

rust 复制代码
fn main() {
    let numbers = vec![10, 20, 30];

    println!("长度:{}", numbers.len());
    println!("是否为空:{}", numbers.is_empty());
}

长度与容量的区别

Vec 有两个重要概念:

go 复制代码
长度 len
容量 capacity

长度表示当前实际元素数量。

容量表示当前已分配空间最多可以容纳多少个元素,而不需要重新分配内存。

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

    println!(
        "长度:{},容量:{}",
        numbers.len(),
        numbers.capacity()
    );

    for number in 1..=10 {
        numbers.push(number);

        println!(
            "长度:{},容量:{}",
            numbers.len(),
            numbers.capacity()
        );
    }
}

当长度超过容量时,Vec 会自动申请更大的内存空间。

具体扩容策略属于实现细节,不应该依赖固定倍数。


预留和释放容量

可以使用:

scss 复制代码
reserve()

提前增加容量:

ini 复制代码
numbers.reserve(100);

如果想尽量释放多余容量,可以使用:

scss 复制代码
shrink_to_fit()
ini 复制代码
fn main() {
    let mut numbers =
        Vec::with_capacity(100);

    numbers.push(10);
    numbers.push(20);

    numbers.shrink_to_fit();

    println!("{}", numbers.capacity());
}

不过频繁调整容量也有成本,通常不需要手动干预。


清空 Vec

使用:

scss 复制代码
clear()
rust 复制代码
fn main() {
    let mut numbers = vec![10, 20, 30];

    numbers.clear();

    println!("{:?}", numbers);
    println!("长度:{}", numbers.len());
}

clear() 会删除所有元素,但已分配容量通常会保留,以便以后继续使用。


判断是否包含元素

rust 复制代码
fn main() {
    let numbers = vec![10, 20, 30];

    println!("{}", numbers.contains(&20));
    println!("{}", numbers.contains(&99));
}

输出:

arduino 复制代码
true
false

查找元素位置

可以结合迭代器和 position()

ini 复制代码
fn main() {
    let numbers = vec![10, 20, 30, 20];

    let position = numbers
        .iter()
        .position(|value| *value == 20);

    println!("{:?}", position);
}

输出:

scss 复制代码
Some(1)

只返回第一个匹配位置。


保留符合条件的元素

可以使用:

scss 复制代码
retain()
ini 复制代码
fn main() {
    let mut numbers =
        vec![1, 2, 3, 4, 5, 6];

    numbers.retain(|number| {
        number % 2 == 0
    });

    println!("{:?}", numbers);
}

输出:

csharp 复制代码
[2, 4, 6]

retain() 会保留闭包返回 true 的元素。


排序 Vec

整数升序排序:

ini 复制代码
fn main() {
    let mut numbers = vec![30, 10, 50, 20];

    numbers.sort();

    println!("{:?}", numbers);
}

输出:

csharp 复制代码
[10, 20, 30, 50]

降序排序:

css 复制代码
numbers.sort_by(|a, b| b.cmp(a));

完整示例:

css 复制代码
fn main() {
    let mut numbers = vec![30, 10, 50, 20];

    numbers.sort_by(|a, b| b.cmp(a));

    println!("{:?}", numbers);
}

浮点数排序

浮点数不能直接使用普通 sort(),因为其中可能存在 NaN

可以使用:

scss 复制代码
sort_by()
css 复制代码
fn main() {
    let mut prices =
        vec![29.9, 10.5, 88.0, 35.6];

    prices.sort_by(|a, b| {
        a.partial_cmp(b).unwrap()
    });

    println!("{:?}", prices);
}

正式项目中需要考虑 NaN,避免随意使用 unwrap()


去除重复元素

dedup() 只会删除相邻的重复元素。

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

    numbers.dedup();

    println!("{:?}", numbers);
}

结果:

csharp 复制代码
[1, 2, 3, 1]

如果要删除所有重复值,可以先排序:

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

    numbers.sort();
    numbers.dedup();

    println!("{:?}", numbers);
}

输出:

csharp 复制代码
[1, 2, 3]

使用 append() 合并 Vec

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

    first.append(&mut second);

    println!("first:{:?}", first);
    println!("second:{:?}", second);
}

输出:

css 复制代码
first:[1, 2, 3, 4, 5, 6]
second:[]

append() 会把 second 中的元素移动到 first


使用 extend() 添加多个元素

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

    numbers.extend([3, 4, 5]);

    println!("{:?}", numbers);
}

输出:

csharp 复制代码
[1, 2, 3, 4, 5]

Vec 与切片

Vec<T> 可以方便地借用为:

css 复制代码
&[T]

例如:

rust 复制代码
fn calculate_total(numbers: &[i32]) -> i32 {
    numbers.iter().sum()
}

fn main() {
    let numbers = vec![10, 20, 30];

    println!("{}", calculate_total(&numbers));
}

函数只需要读取数据时,使用切片参数比直接接收 Vec 更通用。

同一个函数既可以接收数组,也可以接收 Vec

ini 复制代码
let array = [1, 2, 3];
let vector = vec![4, 5, 6];

calculate_total(&array);
calculate_total(&vector);

Vec 与所有权

Vec 可以保存 String

rust 复制代码
fn main() {
    let names = vec![
        String::from("Tom"),
        String::from("Alice"),
    ];

    println!("{:?}", names);
}

不能直接把元素从索引位置移动出来:

ini 复制代码
let name = names[0];

因为索引操作通常返回引用位置,而不能破坏集合结构。

可以借用:

ini 复制代码
let name = &names[0];

也可以克隆:

ini 复制代码
let name = names[0].clone();

或者通过 remove() 取得所有权:

rust 复制代码
fn main() {
    let mut names = vec![
        String::from("Tom"),
        String::from("Alice"),
    ];

    let name = names.remove(0);

    println!("{}", name);
}

数组、Vec 与切片的区别

特性 Array Vec Slice
长度 固定 动态 由引用范围决定
是否拥有数据
添加元素 不支持 支持 不支持
删除元素 不支持 支持 不支持
常见类型 [T; N] Vec<T> &[T]
使用场景 固定数量 动态集合 借用连续数据

可以简单理解:

Array 是固定长度容器。
Vec 是动态长度容器。
Slice 是对容器中一段数据的借用。


实战:商品列表管理

定义一个商品名称列表,支持添加、删除和查找:

rust 复制代码
fn main() {
    let mut products = vec![
        String::from("键盘"),
        String::from("鼠标"),
        String::from("显示器"),
    ];

    products.push(
        String::from("耳机"),
    );

    if let Some(position) = products
        .iter()
        .position(|name| name == "鼠标")
    {
        let removed = products.remove(position);

        println!("删除商品:{}", removed);
    }

    for (index, product) in
        products.iter().enumerate()
    {
        println!(
            "{}. {}",
            index + 1,
            product
        );
    }
}

输出:

markdown 复制代码
删除商品:鼠标
1. 键盘
2. 显示器
3. 耳机

实战:成绩统计

rust 复制代码
fn analyze_scores(
    scores: &[i32],
) -> Option<(i32, f64, i32, i32)> {
    if scores.is_empty() {
        return None;
    }

    let total: i32 = scores.iter().sum();
    let average =
        total as f64 / scores.len() as f64;

    let min = *scores.iter().min()?;
    let max = *scores.iter().max()?;

    Some((total, average, min, max))
}

fn main() {
    let mut scores = vec![88, 59, 95, 76];

    scores.push(100);
    scores.retain(|score| *score >= 60);
    scores.sort_by(|a, b| b.cmp(a));

    match analyze_scores(&scores) {
        Some((total, average, min, max)) => {
            println!("成绩:{:?}", scores);
            println!("总分:{}", total);
            println!("平均分:{:.2}", average);
            println!("最低分:{}", min);
            println!("最高分:{}", max);
        }
        None => {
            println!("没有成绩数据");
        }
    }
}

输出:

css 复制代码
成绩:[100, 95, 88, 76]
总分:359
平均分:89.75
最低分:76
最高分:100

实战:简单任务队列

rust 复制代码
fn main() {
    let mut tasks = vec![
        String::from("学习 Vec"),
        String::from("编写练习"),
    ];

    tasks.push(
        String::from("复习所有权"),
    );

    println!("当前任务:");

    for (index, task) in
        tasks.iter().enumerate()
    {
        println!("{}. {}", index + 1, task);
    }

    if !tasks.is_empty() {
        let finished = tasks.remove(0);

        println!("完成任务:{}", finished);
    }

    println!("剩余任务:{:?}", tasks);
}

需要频繁从队列开头删除元素时,实际项目通常更适合使用:

复制代码
VecDeque

因为 Vec::remove(0) 会移动后面的所有元素。


常见错误

忘记声明 mut

错误:

ini 复制代码
let numbers = vec![1, 2, 3];

numbers.push(4);

正确:

ini 复制代码
let mut numbers = vec![1, 2, 3];

numbers.push(4);

空 Vec 没有指定类型

错误:

ini 复制代码
let values = Vec::new();

如果后面没有足够信息帮助推断类型,编译器会报错。

正确:

rust 复制代码
let values: Vec<i32> = Vec::new();

使用索引访问越界元素

错误:

ini 复制代码
let numbers = vec![1, 2, 3];

println!("{}", numbers[10]);

不确定索引是否合法时,应使用:

csharp 复制代码
numbers.get(10)

遍历时同时修改 Vec 结构

下面的思路通常会产生借用冲突:

typescript 复制代码
for number in &numbers {
    numbers.push(*number);
}

遍历期间已经不可变借用了 numbers,不能同时进行可变修改。

可以先收集新元素,再统一添加。


误以为 dedup() 会删除所有重复值

ini 复制代码
let mut values = vec![1, 2, 1];

values.dedup();

结果仍然是:

csharp 复制代码
[1, 2, 1]

因为重复值不相邻。

如果不关心原顺序,可以先排序再去重。


频繁 remove(0)

csharp 复制代码
values.remove(0);

每次都会移动后面的元素。

如果业务是先进先出的队列,应考虑:

复制代码
VecDeque

最佳实践

函数只读集合时使用切片

推荐:

rust 复制代码
fn print_values(values: &[i32]) {
    println!("{:?}", values);
}

而不是:

rust 复制代码
fn print_values(values: &Vec<i32>) {
}

切片参数更通用。


已知数据量时预分配容量

ini 复制代码
let mut records =
    Vec::with_capacity(1000);

可以减少扩容次数,但不需要为很小的数据过度优化。


不确定索引时使用 get()

csharp 复制代码
if let Some(value) = values.get(index) {
    println!("{}", value);
}

不关心顺序时使用 swap_remove()

ini 复制代码
values.swap_remove(index);

它通常比 remove() 更高效,但会改变元素顺序。


多个业务字段优先使用结构体

不推荐长期使用:

rust 复制代码
Vec<(u64, String, f64, bool)>

更清晰的写法是:

rust 复制代码
struct Product {
    id: u64,
    name: String,
    price: f64,
    enabled: bool,
}

然后使用:

rust 复制代码
Vec<Product>

本章小结

Vec<T> 是 Rust 中最常用的动态集合之一。

通过本文,我们学习了:

  • 使用 vec![] 创建动态数组

  • 使用 Vec::new() 创建空集合

  • push()pop()insert()remove()

  • 使用索引和 get() 访问元素

  • 使用 get_mut() 修改元素

  • 遍历和可变遍历

  • len()capacity() 和动态扩容

  • clear()retain()contains()

  • 排序、查找与去重

  • append()extend()

  • Vec<String> 的所有权问题

  • Vec 与数组、切片的区别

  • 商品列表、成绩统计和任务队列案例

可以记住:

固定数量的数据使用数组。
动态变化的数据使用 Vec
函数只需要读取集合时,参数优先使用切片 &[T]


下一篇预告

下一篇我们将学习 Rust HashMap 哈希表详解,包括:

  • 什么是 HashMap

  • 创建哈希表

  • 插入和更新键值

  • 通过键查询数据

  • get() 与安全访问

  • 删除键值

  • 遍历 HashMap

  • entry() API

  • or_insert() 与计数统计

  • String 所有权问题

  • HashMapVec 的区别

  • 实战:用户信息、商品库存和单词统计

相关推荐
超人不会飞_Jay1 小时前
Go课程2
开发语言·后端·golang
_abab1 小时前
Rust重塑系统编程:从Linux内核到AI推理引擎的2026全景解析
linux·人工智能·rust
奶糖 肥晨1 小时前
一次Spring Boot编译报错排查:三元运算符与包装类型的“隐形陷阱”
java·spring boot·后端
LaughingZhu1 小时前
Product Hunt 每日热榜 | 2026-07-23
前端·神经网络·react.js·搜索引擎·前端框架
To_OC1 小时前
拼路径读文件总踩坑?我把 Node 的 path 和 fs 彻彻底底捋了一遍
javascript·后端·node.js
卷无止境1 小时前
Python生成器与惰性求值:从yield说起的一场"暂停魔法"
后端·python
自然 醒2 小时前
记录We码开发者工具的一个bug
前端·javascript·bug
葡萄城技术团队2 小时前
HTML元素单元格:用自定义 CellType 扩展 SpreadJS 的显示能力
前端·javascript·html
不好听6132 小时前
Fragment:React 的 `<></>` 和原生的 DocumentFragment,同名不同命
前端·react.js·dom