七、常用集合

144 阅读4分钟

七、常用集合

1. Vector

  • 作用

    • 在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值
  • 注意

    • vector 只能储存相同类型的值
    • vector 的结尾增加新元素时,如果没有足够空间将所有所有元素依次相邻存放,就可能会要求分配新内存,并将之前的元素拷贝到新的空间中,所以效率可能相对会比较低
  • vector 存储不同类型的值

    • 当需要在 vector 中储存不同类型值时,我们可以使用一个定义好的枚举来包裹这些值
    • 限制:必须提前知道要存入这个 vector 的所有类型
  • 使用示例

    fn main() {
        // 创建方式 1: 使用 Vector::new
        let mut v1: Vec<i32> = Vec::new();
    
        // 创建方式 2:使用 vec! 宏
        let mut v2 = vec![1, 2, 3];
    
        // 向 vector 内增加元素
        v1.push(4); // 向末尾追加一个元素
    
        // 获取方式 1 - 索引
        let first: &i32 = &v1[0]; // 返回的是引用
    
        // 获取方式 2 - get 方法
        let second: Option<&i32> = v1.get(1); // 返回的是枚举
    
        // 遍历 vector
        for item in &v2 {
            println!("{}", item);
        }
    
        // 来自于官方教程中的一个示例 - 遍历并修改 vector 每个元素的值
        let mut v = vec![100, 32, 57];
        for i in &mut v {
            *i += 50; // 注意这个解引运算符 `*`
        }
    }
    
    

2. 字符串

  • 字符串是作为字节的集合外加一些方法实现的

  • 字节的字符编码格式:UTF-8

  • Rust 中,核心只有一种字符串类型:str ,即字符串 slice,它通常以被借用的形式 ( &str ) 出现

2.1 创建

fn main() {
    // 方式 1:使用 String::new
    let mut s1 = String::new();

    // 方式 2:从字面量转换来
    let mut s2 = "abc".to_string();

    // 方式 3:使用 String::from 转换字面量
    let mut s3 = String::from("abc");

    // 官方教程提示:大部分情况下,String::from 和 .to_string 最终做了完全相同的工作,所以如何选择就是风格问题了
}

2.2 更新

fn main() {
    let mut s = "abc".to_string();

    // 追加一个字符串 slice:push_str
    s.push_str(" abc");

    // 追加单个字符:push
    s.push("d");

    // 使用 + 运算符拼接两个字符串
    let s1 = "aaa".to_string();
    let s2 = "bbb".to_string();
    let s3 = s1 + &s2; // 注意:这里除了第一个变量外,后续的字符串变量都必须取引用
    println!("s2 = {}, s3 = {}", s2, s3); // 不能访问 s1,因为 s1 已经被移动了

    // 使用 format!宏 拼接字符串,与使用 println 的语法一模一样
    let print_content = format!("s2 = {}, s3 = {}", s2, s3);
}

2.3 访问字符

  • 注意:Rust 中,字符串 不支持 按索引访问

    • String 是一个 Vec<u8> 的封装
    • 索引操作预期总是需要常数时间 (O(1))。但是对于 String 不可能保证这样的性能,因为 Rust 必须从开头到索引位置遍历来确定有多少有效的字符
  • 有效的 Unicode 标量值可能会由不止一个字节组成

    • 一个英文字符:1 字节

    • 一个梵文字符:2 字节

    • 一个中文字符:3 字节

    • 一个 emoji 字符:4 字节

      fn main() {
          let a = String::from("a");
          println!("`a` 的长度:{}", a.len());
          let e = String::from("З");
          println!("`З` 的长度:{}", e.len());
          let heart = String::from("❤");
          println!("`❤` 的长度: {}", heart.len());
          let hello = String::from("哈");
          println!("`哈` 的长度: {}", hello.len());
          let hot = "🥵";
          println!("`🥵` 的长度:{}", hot.len());
      }
      
      
  • 按 “字符” 访问

    fn main() {
        // 你可以尝试一下把下面这些字符串按字符打印出来看一下
        print_char("السلام عليكم");
        print_char("Dobrý den");
        print_char("Hello");
        print_char("שָׁלוֹם");
        print_char("नमस्ते");
        print_char("こんにちは");
        print_char("안녕하세요");
        print_char("你好");
        print_char("Olá");
        print_char("Здравствуйте");
        print_char("Hola");
    }
    
    fn print_char(str: &str) {
        for c in str.chars() {
            print!("{}, ", c)
        }
        print!("\n");
    }
    
    

3. Hash Map

  • Hash Map 用于储存一个键类型 K 对应一个值类型 V 的映射
    • 类型:HashMap<K, V>
    • HashMap 通过一个 hash 函数来实现这个映射关系
  • hash 函数
    • 默认使用的是 “密码学安全的 (cryptographically strong)“ 的哈希函数
      • 优点:可以抵抗拒绝服务(Denial of Service, DoS)攻击
      • 缺点:相对性能不够高
    • Rust 支持手动指定一个自定义的 hash 函数

3.1 创建

use std::collections::HashMap;

fn main() {
    // 方式 1:使用 HashMap::new
    let mut map1: HashMap<String, u8> = HashMap::new();

    // 方式 2:使用元组 vector 的 collect 方法
    // 来自官方教程的一个示例
    let teams  = vec![String::from("Blue"), String::from("Yellow")];
    let initial_scores = vec![10, 50];
    // 使用下划线占位即可,Rust 会推断对应的类型
    let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
}

3.2 使用

  • 常用 API
    • get(&key):根据 key 来获取值,可能为 None
    • insert(key, value):不管 hash 表里有没有,都直接插入,会覆盖已有值
    • entry(key).or_insert(value):如果 hash 表里没有,就插入;如果 hash 表里有,就忽略
use std::collections::HashMap;

fn main() {
    hash_map_test();
}

fn hash_map_test() {
    // 创建一个 hash map
    let mut scores: HashMap<String, i32> = HashMap::new();
    let jelly = String::from("Jelly");
    let kate = String::from("Kate");
    // 将数据加入到 hash map 中
    scores.insert(jelly, 50);
    scores.insert(kate, 25);

    // 已经有 Jelly 了,insert 会直接覆盖值
    let jelly = String::from("Jelly");
    scores.insert(jelly, 75);

    // entry 只在没有对应的 key 时才插入值
    scores.entry(String::from("Jelly")).or_insert(50); // 不会插入
    scores.entry(String::from("Yellow")).or_insert(50); // 会插入

    // ! 注意:这个遍历是无序的
    println!("{:?}", scores); // 快捷打印 hash map 的一种方式

    item_get_test(&scores);

    println!("{:?}", scores);
    let changed_scores = update_item_value(&scores);
    println!("{:?}", changed_scores);
}

fn item_get_test(scores: &HashMap<String, i32>) {
    let jelly = String::from("Jelly");
    let jelly_score = scores.get(&jelly);

    // 获取值。在生产中,一定要注意判空
    if !jelly_score.is_none() {
        println!("Jelly's score is {}", jelly_score.unwrap());
    }

    let cherry = String::from("Cherry");
    let cherry_score = scores.get(&cherry);
    if !cherry_score.is_none() {
        println!("Cherry's score is {}", cherry_score.unwrap());
    } else {
        println!("Cherry doesn't has any score...");
    }
}

fn update_item_value(scores: &HashMap<String, i32>) -> HashMap<String, i32> {
    let mut result: HashMap<String, i32> = HashMap::new();

    // 使用 for..in 遍历 hash map
    for (key, value) in scores {
        let value = result.entry(key.clone()).or_insert(value.clone());
        *value += 1; // 注意这个解引运算符
    }

    return result;
}