Rust 学习 08
archive time: 2022-05-22
今天看看 Rust 里的容器
常用容器
这些容器区别于数组的重要的一点就是, 容器的容量会动态变化
Vector
Vec<T>
就是 Vector 类型, 由标准库提供, 可以存储多个相同类型的值
使用 Vec::new()
会返回一个空的 Vector, 所以需要标注类型
let v: Vec<u8> = Vec::new();
我们还可以使用初始值来创建
// int 默认 i32
let v: Vec<u8> = vec![1, 2, 3, 4];
想要往 Vector 类型里面添加元素, 可以使用 push()
// 会自动推断为 Vec<i32>
let v = Vec::new();
v.push(1);
而要读取 Vector 元素, 可以使用 get()
或者 索引 两种方法
let v: Vec<u8> = vec![1, 2, 3];
if let Some(e) = v.get(2) {
println!("{}", e);
}
println!("{}", v[2]);
get()
方法将返回 Option
类型, 故需要 unwrap()
或者其他方式来获取内部值
所有权和借用规则
这点 Vector 和其他 Rust 元素是一样的, 不可以在同一作用域内同时有可变和不可变引用
遍历元素
最常用的就是 for
循环
let v: Vec<u8> = vec![1, 2, 3];
for i in v {
println!("{}", i);
}
还可以在循环内改变值
let mut v: Vec<u8> = vec![1, 2, 3];
for i in &mut v {
*i += 10;
}
for i in v {
println!("{}", i);
}
Example
由于 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),
];
}
String
在 Rust 里, String 使用 UTF-8, 是 byte
的集合
所以可以将 byte
解析为文本
但是在核心, Rust 只有一个字符串类型, 即 &str
创建 String
类型的时候, 可以使用 String::new()
函数, 也可以使用 to_string
方法
实现了 Display
trait 的类型都可以有这个方法
对于字符串字面值还可以使用 String::from()
方法
// 为了之后可以存放东西 mut
let mut s1 = String::new();
// 添加 string 使用 `push_str()`, 添加字符使用 `push()`
s1.push_str("OK");
let s2 = (1.23).to_string();
let s3 = String::from("hello");
println!("s1: {}, s2: {}, s3: {}", s1, s2, s3);
// => s1: OK, s2: 1.23, s3: hello
字符串拼接
字符串拼接可以使用 +
和 format!()
两种方法
+
运算符会隐式调用 add(self, rhs: T) -> Output
方法, 会获取第一个算子的所有权
let s1 = "hello".to_string();
let s2 = s1 + " world";
println!("s2: {}", s2);
// println!("s1: {}", s1); // error
而 format!()
并不会
let s1 = "hello".to_string();
let s2 = format!("{} world", s1);
println!("s2: {}", s2);
println!("s1: {}", s1); // Ok
字符串表示
字符串因为使用了 UTF-8, 所以并不支持索引方式来获取值
而且 len()
方法返回的是字节数, 对于一些特殊字符 (汉字等), 返回值并不等于字符数
Rust 会有 字节, 标量值 和 字型簇 三种方式来
对应 bytes()
和 chars()
方法, 字形簇标准库没有提供对应方法获取
切割字符串是允许的, 但是需要沿着字符边界来切割
即 不允许半个字符
let s = &"hello".to_string()[0..2];
println!("{}", s);
// => he
HashMap<K, V>
用键值对来存储, 可以用 K
来寻找数据
use std::collections::HashMap;
fn main() {
let mut m: HashMap<String, u16> = HashMap::new();
m.insert("John".to_string(), 73);
m.insert("Kevin".to_string(), 82);
println!("{:#?}", m);
}
/*
{
"Kevin": 82,
"John": 73,
}
*/
可以使用 insert()
来插入数据, 而且 HashMap 存放在 heap 上
创建
创建 HashMap 还可以使用 collect()
方法
let name = vec!["Kevin".to_string(), "John".to_string()];
let score = vec![82, 73];
// m 为 HashMap<&String, &i32> 类型
let m = name.iter().zip(score.iter()).collect::<HashMap<_, _>>();
insert()
默认是 Move, 如果实现了 Copy trait 则是复制值
而获取值则是 get(key)
方法
遍历
而遍历, 我们同样还可以使用 for
循环
let mut m: HashMap<String, u16> = HashMap::new();
m.insert("John".to_string(), 73);
m.insert("Kevin".to_string(), 82);
// k : String, v: u16
for (k, v) in m {
println!("{}: {}", k, v);
}
更新
要更新一个现有键对应的值, 可以直接使用 insert()
方法
let mut m: HashMap<String, u16> = HashMap::new();
m.insert("John".to_string(), 73);
m.insert("Kevin".to_string(), 82);
m.insert("John".to_string(), 90); // update John value
如果要防止更新已有键的值, 可以使用 entry()
配合 or_insert()
方法
m.entry("Peter".to_string()).or_insert(90);
例子, 统计词数
for word in text.split_whitespace() {
// 如果不存在, 则插入这个键, 默认值为 0
let cnt = map.entry(word).or_insert(0); // 返回值的可变引用
// 次数加一
*cnt += 1;
}
Hash 函数
默认情况 HashMap 使用了 Hash 函数来加密, 可以抵御一定的 DoS 攻击
但是默认函数不是最快的, 但是安全性更好
可以使用指定不同的 hasher 来切换函数
hasher 指实现了
BuildHasher
trait 的类型
好! 今天就学到这罢