• Rust常见集合


    迄今为止,我们前面遇到的数据类型基本都是栈上存储的。Rust 标准库中包含一系列被称为 集合collections)的非常有用的数据结构。这些集合指向的数据是储存在堆上的,这意味着数据的数量不必在编译时就已知,并且还可以随着程序的运行增长或缩小。本篇我们将了解三个在 Rust 程序中被广泛使用的集合:

    • vector 允许我们一个挨着一个地储存一系列数量可变的值
    • 字符串string)是字符的集合。我们之前见过 String 类型,不过在本章我们将深入了解。
    • 哈希 maphash map)允许我们将值与一个特定的键(key)相关联。这是一个叫做 map 的更通用的数据结构的特定实现。

    vector

    我们要讲到的第一个类型是 Vec,也被称为 vector。vector 允许我们在一个单独的数据结构中储存多于一个的值,它在内存中彼此相邻地排列所有的值。vector 只能储存相同类型的值。

    vector的创建

    新建一个空的vector:

    let v: Vec<i32> = Vec::new();

    为了方便 Rust 提供了 vec! 宏,这个宏会根据我们提供的字面值来创建一个新的 vector。

    let v = vec![1, 2, 3];

    增加元素

    向vector增加元素:

    1. fn main() {
    2. let mut v = Vec::new();
    3. v.push(5);
    4. v.push(6);
    5. v.push(7);
    6. v.push(8);
    7. }

    访问元素

    通过索引或使用 get 方法读取vector里的元素:

    1. fn main() {
    2. let v = vec![1, 2, 3, 4, 5];
    3. let third: &i32 = &v[2];
    4. println!("The third element is {third}");
    5. let third: Option<&i32> = v.get(2);
    6. match third {
    7. Some(third) => println!("The third element is {third}"),
    8. None => println!("There is no third element."),
    9. }
    10. }

    两者区别是使用不合法的索引值会让索引方式的代码发生panic,而get方法因为是返回一个Option<&T>,所以只是返回一个None。

    遍历元素

    使用for in遍历vector:

    1. fn main() {
    2. let v = vec![100, 32, 57];
    3. for i in &v {
    4. println!("{i}");
    5. }
    6. }

    或者在遍历可变vector时进行修改:

    1. fn main() {
    2. let mut v = vec![100, 32, 57];
    3. for i in &mut v {
    4. *i += 50;
    5. }
    6. }

    使用枚举存储不同类型的数据

    由于vector只能存储同类型的数据,对于不同类型的数据,可以将它们跟枚举值进行关联,这样vector就能存储这些枚举值。

    元素的生命周期

    丢弃vector的时候,也会丢弃其中的元素。

    string

    很多 Vec 可用的操作在 String 中同样可用,事实上 String 被实现为一个带有一些额外保证、限制和功能的字节 vector 的封装。

    创建字符串

    新建一个空字符串:

    let mut s = String::new();

    使用 to_string 方法从任何实现了 Display trait 的类型,比如字符串字面值,创建字符串:

    1. fn main() {
    2. let data = "initial contents";
    3. let s = data.to_string();
    4. // 该方法也可直接用于字符串字面值:
    5. let s = "initial contents".to_string();
    6. }

    从字符串字面值创建:

    let s = String::from("initial contents");

    字符串或者字符的附加

    可以通过 push_str 方法来附加字符串 slice,从而使 String 变长:

    1. fn main() {
    2. let mut s = String::from("foo");
    3. s.push_str("bar");
    4. }

    通过push 方法来附加一个单独的字符到string后面:

    1. fn main() {
    2. let mut s = String::from("lo");
    3. s.push('l');
    4. }

    使用+来拼接字符串,需要注意这里使用的是引用:

    1. fn main() {
    2. let s1 = String::from("tic");
    3. let s2 = String::from("tac");
    4. let s3 = String::from("toe");
    5. let s = s1 + "-" + &s2 + "-" + &s3;
    6. }

    使用 format! 宏格式化拼接字符串:

    1. fn main() {
    2. let s1 = String::from("tic");
    3. let s2 = String::from("tac");
    4. let s3 = String::from("toe");
    5. let s = format!("{s1}-{s2}-{s3}");
    6. }

    不支持索引

    Rust 的字符串不支持索引。使用下标索引字符串可能发生错误。

    创建字符串slice

    索引字符串通常是一个坏点子,因为字符串索引应该返回的类型是不明确的:字节值、字符、字形簇或者字符串 slice。因此,如果你真的希望使用索引创建字符串 slice 时,Rust 会要求你更明确一些。为了更明确索引并表明你需要一个字符串 slice,相比使用 [] 和单个值的索引,可以使用 [] 和一个 range 来创建含特定字节的字符串 slice:

    1. #![allow(unused)]
    2. fn main() {
    3. let hello = "Здравствуйте";
    4. let s = &hello[0..4];
    5. }

    里,s 会是一个 &str,它包含字符串的头四个字节。早些时候,我们提到了这些字母都是两个字节长的,所以这意味着 s 将会是 “Зд”。而当你访问到了非字符边界,你就会得到一个panic。

    遍历字符串

    使用 chars 方法遍历字符串的每个char:

    1. #![allow(unused)]
    2. fn main() {
    3. for c in "Зд".chars() {
    4. println!("{c}");
    5. }
    6. }

    bytes 方法返回每一个原始字节:

    1. #![allow(unused)]
    2. fn main() {
    3. for b in "Зд".bytes() {
    4. println!("{b}");
    5. }
    6. }

    HashMap

    HashMap 类型储存了一个键类型 K 对应一个值类型 V 的映射。它通过一个 哈希函数hashing function)来实现映射,决定如何将键和值放入内存中。很多编程语言支持这种数据结构,不过通常有不同的名字:哈希、map、对象、哈希表或者关联数组。

    创建HashMap

    可以使用 new 创建一个空的 HashMap,并使用 insert 增加元素:

    1. fn main() {
    2. use std::collections::HashMap;
    3. let mut scores = HashMap::new();
    4. scores.insert(String::from("Blue"), 10);
    5. scores.insert(String::from("Yellow"), 50);
    6. }

    访问值

    可以通过 get 方法并提供对应的键来从哈希 map 中获取值:

    1. fn main() {
    2. use std::collections::HashMap;
    3. let mut scores = HashMap::new();
    4. scores.insert(String::from("Blue"), 10);
    5. scores.insert(String::from("Yellow"), 50);
    6. let team_name = String::from("Blue");
    7. let score = scores.get(&team_name).copied().unwrap_or(0);
    8. }

    get 方法返回 Option<&V>,如果某个键在哈希 map 中没有对应的值,get 会返回 None。程序中通过调用 copied 方法来获取一个 Option 而不是 Option<&i32>,接着调用 unwrap_orscore 中没有该键所对应的项时将其设置为零。

    遍历HashMap

    可以使用与 vector 类似的方式来遍历哈希 map 中的每一个键值对,也就是 for 循环:

    1. fn main() {
    2. use std::collections::HashMap;
    3. let mut scores = HashMap::new();
    4. scores.insert(String::from("Blue"), 10);
    5. scores.insert(String::from("Yellow"), 50);
    6. for (key, value) in &scores {
    7. println!("{key}: {value}");
    8. }
    9. }

    HashMap和所有权

    对于像 i32 这样的实现了 Copy trait 的类型,其值可以拷贝进哈希 map。对于像 String 这样拥有所有权的值,其值将被移动而哈希 map 会成为这些值的所有者:

    1. fn main() {
    2. use std::collections::HashMap;
    3. let field_name = String::from("Favorite color");
    4. let field_value = String::from("Blue");
    5. let mut map = HashMap::new();
    6. map.insert(field_name, field_value);
    7. // 这里 field_name 和 field_value 不再有效,
    8. // 尝试使用它们看看会出现什么编译错误!
    9. }

    insert 调用将 field_namefield_value 移动到哈希 map 中后,将不能使用这两个绑定。

    如果将值的引用插入哈希 map,这些值本身将不会被移动进哈希 map。但是这些引用指向的值必须至少在哈希 map 有效时也是有效的。

    更新值

    insert不仅可以插入一个键值对,还可以更新键值对。

    map 有一个特有的 API,叫做 entry,它获取键对应的Entry。Entry代表了可能存在也可能不存在的值。Entryor_insert 方法在键对应的值存在时就返回这个值的可变引用,如果不存在则将参数作为新值插入并返回新值的可变引用:

    1. fn main() {
    2. use std::collections::HashMap;
    3. let mut scores = HashMap::new();
    4. scores.insert(String::from("Blue"), 10);
    5. scores.entry(String::from("Yellow")).or_insert(50);
    6. scores.entry(String::from("Blue")).or_insert(50);
    7. println!("{:?}", scores);
    8. }

    or_insert 方法返回这个键的值的一个可变引用(&mut V)。这里我们将这个可变引用储存在 count 变量中,所以为了赋值必须首先使用星号(*)解引用 count:

    1. fn main() {
    2. use std::collections::HashMap;
    3. let text = "hello world wonderful world";
    4. let mut map = HashMap::new();
    5. for word in text.split_whitespace() {
    6. let count = map.entry(word).or_insert(0);
    7. *count += 1;
    8. }
    9. println!("{:?}", map);
    10. }

    参考:

    常见集合 - Rust 程序设计语言 简体中文版

  • 相关阅读:
    【Java Spring】Spring MVC基础
    服务端挂了,客户端的 TCP 连接还在吗?
    最小生成树Boruvka算法
    CentOS8替代盘点
    基于Node.js 和 FFmpeg构建自动化脚本用来转码视频
    Hadoop2.0 YARN cloudra4.4.0安装配置
    JavaEE初阶学习:JVM(八股文)
    【MYSQL】表的约束
    郁金香2021年游戏辅助技术中级班(六)
    在 JavaScript 中循环遍历数组的多种方法
  • 原文地址:https://blog.csdn.net/Mamong/article/details/133021125