8.1-使用向量存储值列表

使用向量存储值列表

我们首先要了解的集合类型是 Vec<T>,也称为向量。向量允许你在单个数据结构中存储多个值,这些值在内存中是连续排列的。向量只能存储相同类型的值。当你有一系列项目时,比如文件中的文本行或购物车中的商品价格,向量非常有用。

创建新向量

要创建一个新的空向量,我们调用 Vec::new 函数,如清单 8-1 所示。

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

清单 8-1:创建一个用于保存 i32 类型值的新空向量

注意这里添加了类型注解。因为我们没有往这个向量里插入任何值,Rust 不知道我们打算存储什么类型的元素。这一点很重要。向量是通过泛型实现的;第10章会介绍如何对自定义类型使用泛型。目前只需知道标准库提供的 Vec<T> 类型可以容纳任意类型。当我们创建特定类型的向量时,可以在尖括号内指定该类型。在清单 8-1 中,我们告诉 Rust v 中的 Vec<T> 将保存 i32 类型元素。

通常,你会用初始值来创建 Vec<T>,Rust 会推断出你想要存储的数据类型,因此很少需要写这种类型注解。Rust 提供了方便的 vec! 宏,它会根据给定的值创建一个新的向量。清单 8-2 创建了一个包含 1、2 和 3 的新 Vec<i32> 向量。整数默认就是 i32 类型,如第3章"数据类型"部分所述。

rust 复制代码
let v = vec![1, 2, 3];

清单 8-2:创建包含初始值的新向量

由于给出了初始 i32 值,Rust 能推断出 v 的类型为 Vec<i32>,因此不需要额外标注。

更新向量

如果先创建空 vector,然后再添加元素,可以使用 push 方法,如清单 8-3 所示:

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

v.push(5);
v.push(6);
v.push(7);
v.push(8);

清单 8-3:使用 push 方法将数值添加到 vector

和所有变量一样,如果想修改其内容,需要用 mut 标记它为可变(详见第3章)。这里放入的是 i32 类型数字,Rust 根据数据自动推断,所以无需显式写成 Vec<i32>

读取 Vector 元素

访问 vector 中某个元素,有两种方式:索引访问和 get 方法访问。在下面例子中,为了更明确地说明返回结果,也加上了返回值得具体类型注释。

清单 8-4 展示了这两种方法:

rust 复制代码
let v = vec![1, 2, 3, 4, 5];

let third: &i32 = &v[2];
println!("第三个元素是 {third}");

let third: Option<&i32> = v.get(2);
match third {
    Some(third) => println!("第三个元素是 {third}"),
    None => println!("没有第三个元素"),
}

清单 8-4:通过索引语法和 get 方法访问 vector 项目

几点说明:

索引从0开始,因此索引为2表示第三个元素。

& 和 [] 返回的是指针引用。

get 方法传入索引后返回 Option<&T>, 可以配合 match 使用。

Rust 提供这两种方式,是为了让程序员选择当越界访问时程序行为------直接 panic 或优雅处理。例如看下尝试获取长度仅五项但请求100号位置,会发生什么(见 清单 8-5):

rust 复制代码
let v = vec![1,2,3,4,5];

let does_not_exist = &v[100]; // 索引越界,将导致 panic!
let does_not_exist = v.get(100); // 返回 None,不会崩溃

清单 8-5:尝试访问只有五项vector里的第100号位置

第一种[]方法遇到不存在的位置会使程序崩溃;适合希望严格保证不会越界场景。

第二种get方法则安全得多,会返回None,可编写逻辑处理异常情况,例如用户输入超范围数字时提示重新输入,更友好且稳定!

借用检查器确保有效引用规则生效(详见第4章),禁止同时存在可变与不可变引用。如以下代码(见 清单 8-6):

rust 复制代码
let mut v=vec![1,2,3,4,5];
let first=&v[0];

v.push(6);

println!("第一个元素是: {first}");

清单 8-6 :持有不可变引用期间尝试修改vector末尾内容

此代码无法编译,通过错误信息指出不能同时拥有不可变借用和可变借用,因为push可能触发内存重分配,使之前持有的不变引用失效。这体现了 Rust 内部对连续内存布局及安全性的保障机制。(更多细节请参阅《The Rustonomicon》)

遍历 Vector 中所有数值

若想依次操作每个元素,而非按索引逐一取,用 for 循环迭代即可。如打印每项(见 清單8-7):

rust 复制代码
let v=vec![100 ,32 ,57 ];
for i in &v {
    println!("{i}");
}

若需修改每项,则迭代可变引用,并利用 * 解引用符改变实际数值 (见 清單8-8):

rust 复制代码
let mut v=vec![100 ,32 ,57 ];
for i in &mut v {
    *i +=50;
}

无论是否可变遍历,都符合借用检查器规则。如果循环体内部插入或删除项目,将产生类似前面提到错误,因为循环本身保持着对整个vector的一致性控制权限制并发改动。

使用枚举支持多种不同数据类混合保存

Vec只能装同一种类的数据,但现实需求常常需要混合不同类别对象一起管理。这时候可以定义枚举(enum),把各种可能的数据包装成统一enum成员,再构建Vec<该enum>

例如表格行里既有整数、浮点、小段文字等字段,就能这样做 (见 清單8-9):

rust 复制代码
enum SpreadsheetCell {
    Int(i32),
    Float(f64),
    Text(String),
}

let row=vec![
   SpreadsheetCell::Int(3),
   SpreadsheetCell::Text(String::from("blue")),
   SpreadsheetCell::Float(10.12)
];

这样就能把不同底层数据封装进同一Vector,同时享受静态强制检查带来的安全性与性能优势。但必须提前确定所有可能出现的数据类别,否则无法满足编译期完整匹配要求。如果运行时才动态决定未知类别,则应考虑 trait 对象方案,第18章讲解相关内容。

总结一下,本章节介绍了一些最常用且实战价值极高的方法来操作Vec,请务必查阅官方API文档以掌握更多丰富功能,比如 pop 用于移除并返回最后一项等辅助函数。

销毁 Vector 时,其内部所有内容也随之释放

像其他结构体一样,当Vector离开作用域即被丢弃(drop),其所含全部资源都会被回收,包括其中保存的数据。(如 清單8-10)

rust 复制代码
{
   let v=vec![1 ,2 ,3 ,4 ];

   // 在此处对 `v` 做一些操作...
} // <- 此处 `v` 离开作用��,被释放,同时里面各项也被释放 

借用检查器确保任何指针都不会悬挂,即只允许在Vector有效生命周期内合法读写其内容。

接下来,让我们继续学习下一种集合------字符串 String!

相关推荐
W.KN2 小时前
机器学习【二】KNN
人工智能·机器学习
淮北4943 小时前
STL学习(十一、常用的算数算法和集合算法)
c++·vscode·学习·算法
糖葫芦君3 小时前
玻尔兹曼分布与玻尔兹曼探索
人工智能·算法·机器学习
TT-Kun3 小时前
PyTorch基础——张量计算
人工智能·pytorch·python
Monkey-旭6 小时前
Android Bitmap 完全指南:从基础到高级优化
android·java·人工智能·计算机视觉·kotlin·位图·bitmap
天若有情6737 小时前
【python】Python爬虫入门教程:使用requests库
开发语言·爬虫·python·网络爬虫·request
哪 吒8 小时前
OpenAI放大招:ChatGPT学习模式上线,免费AI智能家教
人工智能·学习·ai·chatgpt·gemini·deepseek
IT北辰8 小时前
用Python+MySQL实战解锁企业财务数据分析
python·mysql·数据分析
Lucky高8 小时前
selenium(WEB自动化工具)
python
老鱼说AI8 小时前
循环神经网络RNN原理精讲,详细举例!
人工智能·rnn·深度学习·神经网络·自然语言处理·语音识别