8 常见集合
Rust标准库中包含一系列被称为“集合”的非常有用的数据结构。大部分其他数据类型都代表了一个值,但集合可以包含多个值
与内建的元组和数组不同,集合指向的数据存放在堆上,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长和缩小。每种集合都有着不同的功能和成本,我们在实际操作中应该根据场景选择最合适的集合,本章我们将会了解三个被广泛使用的集合
vector允许我们一个挨一个存储一系列数量可变的值
字符串是字符的集合,我们之前见过String类型,本章我们会更进一步了解
哈希map允许我们将值与一个特定的键(key)相关联,这个叫做map的更通用的数据结构的特定实现
标准库中的其他类型的集合,我们可以参考文档
8.1 Vector
第一个集合类型 Vec,也被称为 vector,它允许我们在一个单独的数据结构中存储多余一个的值,所有值在内存中彼此相邻,但是vector智能存储相同类型的值,例如购物车中的产品价格
新建vector
新建一个空的vector来存储i32类型的值,可以调用Vec::new函数
let v :Vec<i32> = Vec::new();
注意:这里我们增加了类型注解,告知我们要用它来存放什么类型的值,但其实vector是用泛型实现的,可以存任何类型,在实际代码中,一旦插入值,Rust就可以推断出值的类型,所以我们很少使用类型注解,直接通过初始化来创建vec。为了方便rust还提供了一个vec!宏,它会根据我们提供的值来创建一个新的Vec,如下:
let v = vec![1,2,3]
更新vector
使用push方法向vector中增加元素
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
println!("{}",v[3]); //8
丢弃vector时也会丢弃其所有变量
类似于其他struct,vector在其离开作用域时会被释放
{
let v = vec![1,2,3,4]
//处理v
} //<- 这里v离开作用域并被丢弃
但是当使用vector元素的引用,情况会变的复杂,我们看下面的处理
读取vector的元素:索引语法或者get方法
let v = vec![1,2,3,4,5];
let third = &v[2];
println!("the third element is {}",third); //the third element is 3
match v.get(2) {
Some(third)=>println!("the third element is {}",third), //the third element is 3
None => println!("there is no third element."),
}
注意:索引值是从0开始的,&和[]会返回一个元素的引用;get方法会以索引为参数返回一个Option<&T>
为什么Rust提供了两种引用元素的方法?因为程序可以选择索引值在vector中没有对应值的情况,我们看看下面的例子
let v = vec![1,2,3,4,5];
let does_not_exist1 = &v[100];
let does_not_exist2 = v.get(100);
对于索引语法,当元素不存在时,程序会发生panic,程序会崩溃
当使用get方法,它不会panic而是会返回None,如果我们想让程序继续运行时可以使用这种方式(索引不存在时可以使用提示信息提示)
match v.get(100) {
Some(third)=>println!("the third element is {}",third), //the third element is 3
None => println!("there is no third element."),
} // there is no third element.
et v = vec![1,2,3,4,5];
let third = &v[2];
v.push(6); //cannot borrow as mutable
println!("the third element is {}",third); //the third element is 3
在相同作用域中不能同时存在可变与不可变引用,这是为什么呢?是由于vector的工作方式,在结尾增加新元素时,空间不足无法存放时,老元素会被拷贝到另一块内存空间中与新元素一起存放,而原来的空间会被释放借用规则会阻止程序陷入悬空的情况
遍历vector中的元素
使用for循环而不是使用索引一个个访问
let v = vec![1,2,3,4,5];
for i in &v{
println!("{}",i);
}
//
1
2
3
4
5
先遍历再改变
let mut v = vec![100,32,57];
for i in &mut v {
*i += 50;
println!("{}",i);
}
//
150
82
107
使用枚举来存储多种类型
vector我们知道,只能存放同种类型的值,但我们有存放不同类型值的需求我们可以使用枚举
enum SpreadSheetCell{
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadSheetCell::Int(3),
SpreadSheetCell::Float(10.12),
SpreadSheetCell::Text(String::from("blue")),
];
Rust在编译时就必须知道vector中类型的原因在于它需要知道存储每个元素到底需要多少内存,第二个好处是可以准确的知道这个vector中允许什么类型。如果vector允许任意类型,那么当对vector元素执行操作时一个或者多个类型的值就可能会遇到错误
但是使用枚举外加match意味着Rust能在编译时就保证总会处理所有的情况
如果在编译程序时,不能确切无疑的知道运行时会存储进vector的所有类型,枚举技术就行不通了,相反可以使用trait对象,十七章我们会讲到它
现在我们了解了一些使用vector的常见方式,标准库中还有vec定义的很多其他使用方法。例如,除了push之外还有一个pop方法,它会移除并返回vector的最后一个元素