Rust 常用的集合


常用的集合

数组和tuple是存在栈上的,这里写的集合是存储在heap上的,也就是说在运行是存储大小是不定的。

Vector 不定长数组

类型签名: Vec

由标准库提供,可存储多个值,只能存储相同类型的数据,值在内存中连续存放。

创建Vector

当想创建一个不定长的数组时,可以使用Vector。

Vec::new();
let c: Vec = Vec::new();

使用初始值创建Vec,使用 vec! 宏。
let c = vec![1, 2, 3];

更新Vector

使用push。

fn main(){
    let mut v = Vec::new();

    // 添加元素后,rust编译就推断出类型,就不需要显示的指明类型了。
    v.push(1)
}

删除 Vector

与其他的struct一样,当Vector离开作用域之后它就被清理掉了,所有的元素也被清理掉了。

读取 Vector中的值

索引和get方法。

fn main(){
    let mut v = vec![1, 2, 3, 4, 5];
    let one: &i32 = &v[0];

    match v.get(0) {
        Some(one) => one,
        None => print!(),
    }
}

用下标的方式超出索引rust会恐慌,match则会匹配到None。

所有权和借用规则。

不能在同一作用域内同时拥有可变和不可变的引用。

fn main(){
    let mut v = vec![1, 2, 3, 4, 5];
    let one = &mut v[0];
    // rust 这里会报错,原因为不能借用多次v为可变的。v和one现在都是可变的,所以报错。
    v.push(1);

    print!("{}\n", one)
}

fn main(){
    let mut v = vec![1, 2, 3, 4, 5];
    let one = &v[0];
    // rust 这里会报错,原因为不能即是可变的又是不变的。
    v.push(1);

    print!("{}\n", one)
}

借用规则防止的是当一个在heap上的数据发生内存地址改变后,引用仍指向原来的地址。此时就会有错。

遍历里面的值

for i in &c {}

Vector + enum

因为Vector里要求类型都是一样的。所以可以使用枚举,在枚举中定义参数接收不同类型,以达到Vector里放不同类型的数据的目的。

enum Money {
    CNY,
    JPY(u16),
    USD(Bank),
}

#[derive(Debug)]
enum Bank {
    HSBC,
    BOC,
}

fn main() {
    let gotoCountry = vec![
        Money::CNY,
        Money::JPY(1000),
        Money::USD(Bank::HSBC),
    ];
}

String

String默认用utf-8。

rust在核心语言层面,只有一个字符串类型:字符串切片str(&str)。

字符串切片

对存储在其他地方,UTF-8编发的字符串引用。字符串字面值存储在二进制文件中,也是字符串切片。

String类型和&str是两个东西。

String类型

来自标准库,而不是核心语言。是一个更高级的类型。

创建新的字符串

String::new();
let mut s = String::new();
new()方法是不能直接初始化值。

to_string();
let s = "hello".to_String();

String::from();
let s = String::from("hello.");

更新String

push_str(); 把一个字符串切片附加到String里。

let mut s = String::from("hello.");
s.push("World");

push(); 把单个字符附加到String里。

  • 拼接,要求前面是String类型,后面是String引用。
    let s = s1 + &s2
    拼接后s1不能使用了。

format!();
format("{}{}{}", a, b, c)
类似python。但不会取得所有权。

String 索引

rust字符串不支持索引语法访问。

len();
let s = String::from("dd").len();

因为String底层是一个 vec 的不定长列表,里面装的是unicode字节值,所以杜绝了这种方法。

另外,所以操作的复杂度是O(1),String无法保证长度,所以需要遍历所有内容。

字节,标量值和字形簇

bytes 字节, chars 标量值, ...

切割String

let s = &hello[0..4];

必须沿着字符的边界切割。

HashMap

HashMap 键值对,类似字典。数据是存在heap上,hashmap是同构的,所有的key是一种类型,所有的value是一种类型。

创建hashmap

创建空 HashMap: new();

添加数据:insert();

let mut account: HashMap = HashMap::new();

let mut account = HashMap::new();
account.insert(String::from("achu"), 123);

需要手动引入use std::collections::HashMap;

collect
在元素类型为tuple的Vector上使用collect方法可以组建一个hashmap。本质是Vector。

collect方法可以把数据整合成很多种集合类型,包括 hashMap。

合并两个vec成为hashmap

let teams = vec!["blue".to_string(), "red".to_string()];
let scores = vec![10, 50];
let res: HashMap<_, _> = teams.iter().zip(scores.iter()).collect();
println!("res = {:#?}", res); 

遍历hashMap

推荐:使用for (i, k) in &hashmapE {}

.get()方法配合match去匹配返回的option枚举使用。

HashMap 所有权

  • 对于实现了copy trait的类型(eg: i32),值会被赋值到hashmap中。
  • 拥有所有权的值,值会被移动,所有权会被转移给hashmap,如果将值的引入插入到hashmap里,值本身不会移动。
use std::collections::HashMap;
fn main() {
    let username = String::from("achu");
    let password = String::from("1234");

    let mut account = HashMap::new();

    // 此时的两个变量的所有权移交给了hashmap
    account.insert(username, password);

    // 这里再调用就会报错
    println!("{}{}", username, password)
}

更新hashmap

hashmap大小可变,每个k只能对应一个v。

替换

插入一个同样的key,不同的value就会被替换。

不存在key再插入

entry(k);,返回枚举,查询key是否存在。

or_insert(5),key存在,返回到对应的V的一个可变引用。key不存在,就将方法参数作为k的新值插进去,返回这个值的可变引用。

use std::collections::HashMap;
fn main() {
    let text = "hello world";
    let mut map = HashMap::new();

    for i in text.split_ascii_whitespace() {
        // 这里是对hashmap传值,如果有就返回这个value的引用,没有就设置为0然后返回value的引用
        let count = map.entry(i).or_insert(0);

        // 这里要解引用
        *count += 1;
    }

    print!("{:#?}", map)
}