概述

  • 参考文档:std
  • String、Vec、HashMap等常用数据结构都在标准库std里,不需要第三方依赖
  • 语法基础见Rust 基础语法,涉及所有权的地方见Rust 所有权与借用
  • 常见需求对应的容器:
flowchart TD
  Q{"要存什么"} -->|"一列同构元素,按下标访问"| V["Vec<T>"]
  Q -->|"两端进出"| D["VecDeque<T>"]
  Q -->|"键值映射"| H{"需要按键有序?"}
  H -->|"否"| M["HashMap<K, V>"]
  H -->|"是"| B["BTreeMap<K, V>"]
  Q -->|"去重或成员判断"| S["HashSet<T>"]
  Q -->|"文本"| T["String"]

String

  • String持有堆上可增长的UTF-8数据,&str是对字符串数据的借用视图,切片与所有权背景见Rust 基础语法

  • 创建:String::from与.to_string()等价,String::new()创建空串后续再追加

    rust
    let a = String::from("hello");
    let b = "hello".to_string();
    let c = String::new();
  • 追加与插入:push追加单个char,push_str追加&str,insert按字节下标插入

    rust
    let mut s = String::from("he");
    s.push('l');       // 追加字符
    s.push_str("lo");  // 追加 &str
    s.insert(0, 'H');  // 下标按字节计,多字节字符前插入要小心
    
  • 拼接:+会移走左侧的String,多个部分拼接用format!,它不取走任何参数

    rust
    let s1 = String::from("ab");
    let s2 = String::from("cd");
    let s3 = s1 + &s2;                 // s1 被移走,此后不可用
    let s4 = format!("{}{}", s2, s3);  // s2 与 s3 都还能用
    
  • 不支持s[0]这样的下标访问:字符串按UTF-8逐字节存储,字节下标可能落在字符中间,编译器直接拒绝

    • 按字符遍历用chars(),按字节遍历用bytes(),字节数用len()

      rust
      let s = "你好";
      s.len();           // 6:两个汉字共 6 个 UTF-8 字节
      s.chars().count(); // 2:字符数
      
    • chars()数的是Unicode标量值,带音调符号或组合字符的字素会被数成多个

  • 与&str互转:&string在需要&str的位置自动转换,反向用.to_string()

  • 遍历与查找:chars()产出char,find返回第一个匹配的位置

    rust
    for c in "rust".chars() { }
    if let Some(i) = "hello".find('l') { } // i 是字节下标 2
    

Vec

  • 动态数组:元素连续存放在堆上,长度可变,日常最常用的序列容器

  • 创建:vec!宏带初值,Vec::new()创建空的

    rust
    let v = vec![1, 2, 3];
    let mut w: Vec<i32> = Vec::new();
    w.push(4);
  • 与数组的取舍:长度编译期已知且小用[T; N](栈上、零开销),需要增长或长度依赖运行时数据用Vec

  • 增删:push尾部追加,pop尾部取出返回Option<T>,insert与remove按下标操作并整体挪动后面的元素

    rust
    let mut v = vec![1, 2, 3];
    v.push(4);              // [1, 2, 3, 4]
    v.remove(0);            // [2, 3, 4],后面元素前移
    let last = v.pop();     // Some(3),v 变为 [2, 4]
    
  • 访问:下标越界直接panic,get返回Option交由调用方处理

    rust
    let v = vec![1, 2, 3];
    let a = v[0];      // 越界时 panic
    let b = v.get(3);  // None
    
  • 遍历:&v只读,&mut v可写,直接v会消费元素

    rust
    for x in &v { }        // x 是 &i32
    for x in &mut v { *x += 1; }
    for x in v { }         // v 被移走,之后不可用
    
  • 常用方法:len、is_empty、contains、clear、sort,配合迭代器能替代多数手写循环

    rust
    let mut v = vec![3, 1, 2];
    v.sort();                                   // [1, 2, 3]
    let d: Vec<i32> = v.iter().map(|x| x * 2).collect(); // [2, 4, 6]
    
  • 存放不同类型的常见做法:定义enum让变体承载各类型,再放进Vec,见Rust 枚举与错误处理

HashMap

  • 键值对容器:键唯一,平均O(1)查找,迭代顺序不确定

  • 创建与插入:HashMap::new()配合insert,键已存在时新值覆盖旧值,返回被覆盖的Option<V>

    rust
    use std::collections::HashMap;
    let mut scores = HashMap::new();
    scores.insert("alice", 90);
    let old = scores.insert("alice", 95); // Some(90)
    
  • 取值:get按 key 借用,返回Option<&V>,match或if let解包后使用

    rust
    if let Some(score) = scores.get("alice") {
        println!("{score}");
    }
  • 计数的惯用写法:entry拿到键的占位枚举,or_insert在键不存在时先写入默认值,再就地修改

    rust
    let text = "a b a c a b";
    let mut counts: HashMap<&str, i32> = HashMap::new();
    for word in text.split_whitespace() {
        *counts.entry(word).or_insert(0) += 1;
    }
  • 遍历与删除:for (k, v) in &map遍历,remove按键删除并返回Option<V>

  • 所有权:insert把键与值移入HashMap,之后原变量按所有权规则处理,Copy类型则直接复制

  • 对键的要求:必须实现Eq与Hash,String、整型、&str与元组满足,浮点类型不满足

  • 需要按键有序遍历或做范围查询时改用BTreeMap

HashSet

  • 只存键不存值:成员判断平均O(1),用于去重与已访问标记

    rust
    use std::collections::HashSet;
    let mut set = HashSet::new();
    let first = set.insert(1); // true
    let dup = set.insert(1);   // false,条目不变
    set.contains(&1);          // true
    
  • 集合运算:intersection交集、union并集、difference差集,返回迭代器

    rust
    let a: HashSet<i32> = vec![1, 2, 3].into_iter().collect();
    let b: HashSet<i32> = vec![2, 3, 4].into_iter().collect();
    for x in a.intersection(&b) { } // 依次产出 2, 3
    

其他集合

  • VecDeque<T>:双端队列,头部与尾部都能O(1)进出,队列与滑动窗口用
  • BTreeMap<K, V>与BTreeSet<T>:按键有序存储,支持范围查询与有序遍历
  • LinkedList<T>:双向链表,缓存不友好也没有随机访问,多数场景应改用Vec或VecDeque

输入输出

  • 打印:print!不换行,println!换行,eprintln!输出到标准错误,{}按显示格式,{:?}按调试格式

    rust
    println!("x = {}, y = {}", 1, 2);
    println!("{:?}", vec![1, 2]); // 集合类型用调试格式打印
    
  • 读一行:stdin().read_line把一行读进String并返回io::Result,expect在出错时终止并报告

    rust
    use std::io::stdin;
    let mut line = String::new();
    stdin().read_line(&mut line).expect("读入失败");
    let n: i32 = line.trim().parse().expect("不是整数");
  • 读入并回显的完整例子:print!不带换行时要手动flush才能及时显示

    rust
    use std::io::{stdin, stdout, Write};
    print!("你的名字: ");
    stdout().flush().unwrap();
    let mut name = String::new();
    stdin().read_line(&mut name).unwrap();
    println!("你好, {}", name.trim());