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 只能存放相同类型元素, 但有时候需要存放不同类型元素

这时候, 我们就可讲 enumVector 结合使用

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 的类型


好! 今天就学到这罢