Rust学习笔记(六)|Rust 中的常用集合(Vector、String、HashMap)
本篇文章包含的内容
- 1 Vector
- 1.1 定义 Vector
- 1.2 访问 Vector中的元素
- 1.3 遍历 Vector
- 1.4 使用 Vector 和 Enum 配合存放多种数据
- 2 String
- 2.1 定义 String
- 2.2 更新 String
- 2.3 访问 String 的一部分
- 3 HashMap
- 3.1 定义 HashMap
- 3.2 HashMap 的所有权
- 3.3 访问 HashMap
- 3.4 更新 HashMap
- 3.5 Hash 函数
Rust标准库提供了一些常用的集合,例如Vector、String和HashMap,这些集合的共同特点是数据存储在Heap(堆)内存上,可以在运行时动态地确定他们的大小。
1 Vector
1.1 定义 Vector
Vector由Rust标准库提供,写法为Vec<T>
,允许在内存中存放多个相同类型的数据,这些数据在内存中连续存放。使用下面的方法创建一个Vector。
let v1: Vec<i32> = Vec::new(); // 编译器无法自动推断时显式指定Vector的类型let mut v2 = Vec::new();
v2.push(1); // 编译器自动推断v2类型let v3 = vec![1, 2, 3]; // 使用vec!宏
Vector离开作用域时数据就会被清理(Drop)。
1.2 访问 Vector中的元素
使用下面两种方式读取Vector中的元素:
- 使用索引:访问越界时程序会panic
get
方法:返回Option枚举,访问越界时程序会返回None
fn main() {let v = vec![1, 2, 3, 4, 5];let third: &i32 = &v[2];println!("The third element is {}", third);match v.get(2) {Some(third) => println!("The third element is {}", third),None => println!("There is no element!"),}
}
之前在介绍所有权时了解过,一个变量不可以同时拥有可变的引用和不可变的引用。这一特性对Vector也有效,即使只引用了Vector中的一部分:
fn main() {let mut v = vec![1, 2, 3, 4, 5];let first = &v[0]; // 不可变借用v.push(6); // 可变借用,非法println!("The first element is {}", first);
}
Vector在内存中是连续的,如果允许在引用部分元素时在末尾添加元素,那么添加元素时就可能重新分配内存,这样就可能导致原来数据的位置发生改变,所以Rust直接杜绝了这种行为。
1.3 遍历 Vector
通常使用for
循环遍历Vector:
fn main() {let mut v = vec![100, 50, 150];for i in &v {println!("{}", i);}for i in &mut v {*i += 50; // 解引用println!("{}", i);}
}
1.4 使用 Vector 和 Enum 配合存放多种数据
在之前介绍枚举时,我们了解到可以使用枚举嵌入数据的方式定义枚举,既然枚举是确定的一种类型,那么就可以将其放入Vector中。
enum SpreadsheetCell {Int(i32),Float(f64),Text(String),
}fn main() {let row = vec![SpreadsheetCell::Int(3),SpreadsheetCell::Text(String::from("blue")),SpreadsheetCell::Float(10.12),];
}
注意,这种方法只适用于Vector中“存放”的可能的数据类型是详尽已知的,如果数据类型有无限种可能,那么枚举也无法定义。这时候可以使用Trait对象来解决,这种方法以后再了解。
2 String
在Rust中,字符串是由标准库提供的基于字节组织的集合,并提供了一系列方法使得我们可以解码字符串的内容。Rust核心代码层面字符串指&str
,在标准库层面又提供了String
。String
和&str
都采用UTF-8编码,其中存放的字符都是Unicode字符,它不仅支持汉字,甚至支持阿拉伯语、梵文等特殊字符的解码。标准库中还提供了其他的字符串类型,例如OsString
、OsStr
、CString
、CStr
,但是这里不作细致讨论,入门时仅了解String
即可。
2.1 定义 String
定义String
可以使用to_string()
方法或者String::from()
函数:
fn main() {let s1 = "initial string".to_string(); // 适用所有实现了Display方法的类型let s2 = 123.to_string();let s3 = String::from("hello");let mut s4 = String::new(); // 创建一个空 String
}
2.2 更新 String
可以对String
进行添加或者拼接操作:
fn main() {let mut s1 = "initial string".to_string();s1.push_str(" foo"); // 添加一个&strs1.push('b'); // 添加一个字符println!("{}", s1);
}
特别地,可以使用+
对字符串进行拼接操作,+
前后的数据类型如下所示,+
前的String
所有权会发生丢失(所有权移入add
函数):
fn main() {let s1 = String::from("hello");let s2 = String::from(" world");let s3 = s1 + &s2;println!("{}", s3);// println!("{}", s1); // s1 所有权丢失println!("{}", s2);
}
使用format!()
宏可以更加方便得拼接字符串,并且这种方法不会获得任何变量的所有权:
fn main() {let s1 = String::from("tic");let s2 = String::from("tac");let s3 = String::from("toe");let s = format!("{}-{}-{}", s1, s2, s3); // 不会获得s1, s2, s3的所有权println!("{}", s);
}
2.3 访问 String 的一部分
Rust不支持使用索引访问String
类型的部分元素。String
本质是对Vec<u8>
的包装,使用len()
方法可以获得该String
占用的字节数。
如果要访问字符串的一部分,可以使用之前学习过的字符串切片&str
,但是切片时必须保证切片的位置是字符的边界,如果切片位置不是字符边界,程序就会panic。
Rust有三种看待字符的方式:字节、Unicode标量值、字形簇,其中字形簇是最接近我们理解字符的方式。但是获取字形簇相对较为复杂,标准库没有实现遍历它的方法,这里仅作了解即可。
fn main() {let s = "你好";for b in s.bytes() { // 遍历字节println!("{}", b);}for b in s.chars() { // 遍历Unicode标量值println!("{}", b);}
}
3 HashMap
3.1 定义 HashMap
HashMap
是一种通过键值对组织数据的数据类型,类型为HashMap<K, V>
,其中K
和V
都是确定的类型。一个HashMap
中所有K
必须是同一种类型,所有V
也必须是同一种类型(同构)。它内部存在一个Hash
函数,描述了如何在内存中存储数据。
这种数据结构有点像Python中的字典。HashMap
并没有预导入,需要使用use
关键字对它进行导入,使用下面的方法定义一个HashMap:
use std::collections::HashMap;fn main() {let mut scores = HashMap::new();scores.insert("blue", 50); // 类型自动推断
}
另一种常用的创建HashMap
的方法是使用collect
方法,它使用元组Tuple进行创建。collect
方法科技把数据整合成多种数据类型,包括HashMap
,所以在创建时需要显式指明返回值的类型。
use std::collections::HashMap;fn main() {let teams = vec![String::from("Blue"), String::from("Yellow")];let initial_scores = vec![10, 50];let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect(); // 两个迭代器通过zip方法返回一个元组
}
3.2 HashMap 的所有权
对于实现了Copy Trait的数据,值会被复制到HashMap中;对于拥有所有权的类型,值会发生移动(Move),所有权会转移给HashMap,除非使用了引用。使用引用初始化时,数据也只有一个,所以在作用域内必须保证数据一直有效。
use std::collections::HashMap;fn main() {let field_name = String::from("Favorite color");let field_value = String::from("Blue");let mut map = HashMap::new();// map.insert(field_name, field_value); // 初始化时所有权会发生转移map.insert(&field_name, &field_value);println!("{}: {}", field_name, field_value);
}
3.3 访问 HashMap
使用get
方法访问HashMap中的元素,get
方法会返回一个Option枚举,所以就很适合使用match
语句。
use std::collections::HashMap;fn main() {let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Yellow"), 50);let team_name = String::from("Blue");let score = scores.get(&team_name);match score {None => println!("Team not exist"),Some(s) => println!("{}", s),}
}
也可以使用for
语句遍历HashMap
:
use std::collections::HashMap;fn main() {let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Yellow"), 50);for (k, v) in &scores { // 模式匹配println!("{}: {}", k, v);}
}
3.4 更新 HashMap
在更新HashMap时,会出现以下几种情况,对每一种情况Rust标准库都内置了方案来方便地解决:
- K不存在:将新的K和V添加进HashMap
- K存在:
- 替换现有的V
- 保留现有的V,忽略新的V
- 合并现有的和新的V
替换现有的V:如果向HashMap插入一对KV,然后再插入相同的K,但是V不同,这时新的V就会替换掉现有的V。
use std::collections::HashMap;fn main() {let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Blue"), 50);println!("{:?}", scores); // {"Blue": 50}
}
只有插入的K不存在才会执行插入:使用entry
方法。entry
方法会返回一个Entry
枚举,通过这个枚举判断K是否存在,配合or_insert
方法将K不存在时的新的KV插入HashMap,如果K存在则不执行任何操作:
use std::collections::HashMap;fn main() {let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.entry(String::from("Yellow")).or_insert(20);scores.entry(String::from("Blue")).or_insert(50);println!("{:?}", scores); // {"Yellow": 20, "Blue": 10}
}
基于现有V来更新V:or_insert
方法总会返回V的可变引用,如果K存在,则返回K对应的V的可变引用;如果K不存在,则返回新插入的值的可变引用。
use std::collections::HashMap;fn main() {let text = "hello world wonderful hello";let mut scores = HashMap::new();for item in text.split_whitespace() {let count = scores.entry(item).or_insert(0);*count += 1;}println!("{:?}", scores); // {"hello": 2, "world": 1, "wonderful": 1}
}
3.5 Hash 函数
默认情况下,HashMap使用加密功能强大的Hash函数,可以抵抗拒绝服务(Dos)攻击,它不是最快的Hash算法,但是拥有较好的安全性。
可以指定不同的hasher来切换Hash函数,hasher是实现BuildHasher trait的类型。这里仅作了解即可。
原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、FPGA方面的学习笔记。