Rust的标准库里有一系列数据结构,称作集合(collections)。这里讲解三种被广泛使用的集合:vector、string和hashmap。
Vector🔗
vector是一个动态数组,可以存储多个值,并且值类型相同。vector是可变的,可以添加、删除和修改元素。
创建vector🔗
创建一个空的vector:
let v: Vec<i32> = Vec::new();
这里创建了个叫v的vector,类型是i32,初始为空。
创建一个包含初始值的vector:
let v = vec![1, 2, 3];
这里使用了vec!宏,这个宏会根据提供的类型判断出vector的类型,并创建一个包含初始值的vector。
更新vector🔗
向vector中添加元素:
let mut v = Vec::new();
v.push(5);
v.push(6);
v.push(7);
v.push(8);
...
这里使用了push方法向vector中添加元素。
读取vector中的元素🔗
读取vector中的元素有两种方式:索引和get方法。
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
println!("The third element is {}", third);
let third: Option<&i32> = v.get(2);
match third {
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element."),
}
这里分别使用索引和get两种方法来读取vector中的元素,可以看到get方法的返回值是一个Option类型,这是因为索引可能超出vector的范围,所以需要使用Option来处理这种情况。
❗ 重要
注意:使用索引访问vector中的元素时,如果索引超出了vector的范围,会导致panic,而使用get方法访问时,如果索引超出了vector的范围,会返回None。
遍历vector🔗
可以使用for遍历vector中的元素:
let v = vec![1, 2, 3, 4, 5];
for i in &mut v {
println!("{i}");
}
for i in &mut v {
*i += 50;
}
这里分别使用了for遍历vector中的元素,以及for遍历vector中的可变引用,并修改了元素。
使用枚举来存储多个类型的值🔗
可以使用枚举来存储多个类型的值,例如:
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
这里定义了一个枚举SpreadsheetCell,它有三个变体:Int、Float和Text,分别对应i32、f64和String类型。然后使用vec!宏创建了一个包含SpreadsheetCell类型的vector。
String🔗
String是一个可变的、动态分配的UTF-8编码的字符串,是一个vector的封装,可以存储多个字符。
创建String🔗
创建一个空的String:
let mut s = String::new();
这里创建了一个叫s的String,初始为空。
创建带初始值的字符串:
let s = String::from("initial contents");
let s = "initial contents".to_string();
这里分别使用了String::from和to_string方法创建了一个带初始值的String。
ℹ️ 注意
注意:字符串是UTF-8编码的,所以可以存储任何Unicode字符。
更新String🔗
使用push_str方法添加slice到String中,使用push方法添加单个字符到String中:
let mut s = String::from("foo");
s.push_str("bar");
s.push('l');
这里分别使用了push_str和push方法向String中添加了slice和字符。
使用’+’或format!宏连接字符串🔗
可以使用+连接字符串:
let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // note s1 has been moved here and can no longer be used
这里使用了+运算符连接了两个String,注意s1被移动了,所以不能再使用s1。s2是一个引用,在这里会转移成&str,所以可以连接,之后也可以继续使用。
在连接多个字符串时,可以使用format!宏:
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3);
这里使用了format!宏连接了三个String,并返回了一个新的String,format!宏和println!宏类似,只不过不是打印而是返回结果。
String索引🔗
Rust中的String不可索引,String是Vec
ℹ️ 注意
另外:索引操作预期需要常数时间O(1),但Rust计算一个字符需要遍历整个字符串来确认有效字符。
使用索引创建字符串slice是可行的:
let hello = "Здравствуйте";
let s = &hello[0..4];
这里使用了索引创建了一个字符串slice,s的值是“Зд“,注意索引是从0开始的,所以s的长度是4。(这些字符每个占用2个字节)
🔥 谨慎
如果获取&hello[0..1],会报错,因为第一个字符是两个字节。
遍历String🔗
遍历String需要明确表示的是字符还是字节,可以使用chars方法遍历字符,使用bytes方法遍历字节:
for c in "Зд".chars() {
println!("{}", c);
}
for b in "Зд".bytes() {
println!("{}", b);
}
这里分别使用了chars和bytes方法遍历了字符串中的字符和字节,输出如下内容:
З
д
208
151
208
180
这里使用了chars方法遍历了字符串中的字符,使用bytes方法遍历了字符串中的字节。
HashMap🔗
HashMap<K,V>储存了一个键值对的映射。哈希map用于需要任何类型作为key来寻找数据的情况,这里简单介绍HashMap。
创建HashMap🔗
创建一个空的HashMap:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
这里使用了HashMap::new方法创建了一个空的HashMap,然后使用insert方法向HashMap中添加了两个键值对。
像vector一样,hashmap的键类型和值类型必须相同,这里使用了String和i32类型。
ℹ️ 注意
注意:要先使用use std::collections::HashMap引入HashMap,由于HashMap相对Vector和String使用频率较低,所以没被prelude自动引用。
访问HashMap中的值🔗
使用get方法访问HashMap中的值:
use std::collections::HashMap;
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).copied().unwrap_or(0);
get方法返回一个Option类型,如果键存在,则返回Some值,否则返回None,这里使用了copied方法获取一个Option
遍历HashMap中的值也可以用for:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
for (key, value) in &scores {
println!("{}: {}", key, value);
}
这里使用了for遍历了HashMap中的键值对,并打印了键和值,输出如下:
Blue: 10
Yellow: 50hashmap和所有权🔗
对于实现Copy trait的类型,例如i32,HashMap会将值复制到哈希map中,对于String这样有所有权的值,HashMap会将值的移动并获取所有权:
use std::collections::HashMap;
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");
let mut map = HashMap::new();
map.insert(field_name, field_value);
这里创建了一个String类型的field_name和field_value,然后使用insert方法将它们插入到HashMap中,之后field_name和field_value的所有权被移动到了HashMap中,所以不能再使用它们。
如果将值的引用插入到HashMap中,那么值不会被移动,但是这些引用所指的值必须至少在hashmap有效时有效。
更新HashMap🔗
每一个键只能关联一个值,所以在处理一个键时,需要先检查键是否存在,如果存在则更新值,如果不存在则插入键值对。
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
println!("{:?}", scores);
这里先插入了一个键值对,然后又插入了一个键值对,但是键是相同的,所以第二个键值对会覆盖第一个键值对,输出如下:
{"Blue": 25}
使用entry可以更简洁的更新HashMap:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores);
entry返回值是一个枚举Entry,代表可能存在也可能不存在的值。使用Entry的or_insert方法在键值存在时返回这个值的可变引用,如果不存在则将参数作为值插入,并返回这个新值的可变引用。这里使用了or_insert方法更新了HashMap中的值,输出如下:
{"Yellow": 50, "Blue": 10}
如果要根据旧值更新一个值,考虑如下代码:
//这里是计数一个字符串中每一单词出现的次数
use std::collections::HashMap;
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map);
这里使用了entry方法获取了键的可变引用,然后使用*count += 1更新了值,输出如下:
{"hello": 1, "world": 2, "wonderful": 1}
其中,split_whitespace方法返回一个由空格分割text值子slice的迭代器,or_insert方法返回一个可变引用,所以需要使用*count来更新值。
总结🔗
本文介绍了Rust中的Vector、String和HashMap,Vector是动态数组,String是字符串,HashMap是哈希表,它们都是Rust中常用的集合类型,其中HashMap使用时要用use std::collections::HashMap引入,而Vector和String可以直接使用。