• Rust权威指南之通用集合类型


    简述

    Rust标准库包含了一系列非常有用的被称为集合的数据结构。本篇文章我们主要介绍:动态数组、字符串和哈希映射。

    一. 动态数组

    动态数组:Vec。动态数组允许你在单个数据结构中存储多个相同类型的值,这些值会彼此相邻地排布在内存中。动态数组非常合适在需要存储一系列相同类型值的场景中使用。

    1.1. 创建数组

    Rust创建动态数组有以下几个方法:

    • Vec::new():创建一个空动态数组
    • Vec::with_capacity(10):创建一个指定容量的动态数组
    • vec![]:创建一个持有初始值的数组
    • vec![n; m]:创建并初始化vec,共m个元素,每个元素都初始化为n
    let vec: Vec<i32> = Vec::new(); // []
    let vec1: Vec<i32> = Vec::with_capacity(10); // []
    let vec2 = vec![1, 2, 3]; // [1,2,3]
    let vec3 = vec![1; 5]; // [1,1,1,1,1]
    
    • 1
    • 2
    • 3
    • 4

    1.2. 修改和访问

    如果需要使vec可以修改,需要使用mut关键字。例子:

    let mut vec = Vec::new(); // 此时可以不用标明数据类型,当存入数据的时候,编译器可以推到出数据类型。
    vec.push(10);
    vec.push(11);
    vec[1] = 12;
    println!("{:?}", vec); // 修改前 [10, 11]
    vec[1] = 12; // 修改
    println!("{:?}", vec); // 修改后 [10, 12]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果访问数组的数据,可以使用索引来访问vec中的元素。索引越界之后,将在运行时panic报错。

    let v = vec![5, 6, 7];
    let idx: usize = 1
    let i = v[idx]; // 5
    
    • 1
    • 2
    • 3

    再有如果我们想引用动态数组中元素可以使用引用和get方法:

    let v = vec![5, 6, 7];
    let i = v[1]; // i32
    println!("{}", i); // 6
    let j = &v[2]; // &i32
    println!("{}", j); // 7
    
    • 1
    • 2
    • 3
    • 4
    • 5

    vecget方法有两种:

    • get:获取指定索引处的元素引用或范围内元素的引用,如果索引越界,返回None
    • get_mut:获取元素的可变引用或范围内元素的可变引用,如果索引越界,返回None
    let mut v = vec![5, 6, 7];
    let a = v.get(1);  // Option<&i32>
    println!("{}", a.unwrap()); // 6
    v.get_mut(1).map(|e| {
        *e = 10
    });
    v.get(1).map(|data| {
        println!("{}", data) // 10
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    1.3. 数组遍历

    数组遍历可以使用for in

    let v = vec![5, 6, 7];
    for i in &v {
        println!("{}", i);
    }
    
    • 1
    • 2
    • 3
    • 4

    如果想在遍历的时候修改数组:

    let mut v = vec![5, 6, 7];
    for i in &mut v {
        *i += 50;
    }
    
    • 1
    • 2
    • 3
    • 4

    1.4. 枚举和数组

    从上面的例子我们可以知道,Vec只能存储一种类型的数据,那么如何突破这点呢?此时就可以用枚举突破,上例子:

    enum SpreadsheetCell {
        Int(i32),
        Float(f64),
        Text(String),
    }
    
    let v = vec![
        SpreadsheetCell::Int(3),
        SpreadsheetCell::Float(20.23),
        SpreadsheetCell::Text(String::from("hello world")),
    ];
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    1.6. 数组常用方法

    接着我们可以看一下,vec常用的一些方法:

    方法描述
    len()返回vec的元素数量
    is_empty()vec是否为空
    push()vec尾部插入元素
    pop()删除并返回vec尾部的元素,为空则返回None
    insert()在指定位置插入元素
    remove()删除指定索引处的元素并返回被删除的元素,索引越界将panic报错退出
    clear()清空vec
    append()将另一个vec中所有元素追加移入vec中,移动后另一个vec为空
    truncate()vec截断到指定长度,多与的元素被删除
    retain()保留满足条件的元素,即删除不满足条件的元素
    drain()删除vec中指定范围的元素,同时返回一个迭代该范围所有元素的迭代器
    split_off()vec从指定索引处切分成两个vec,索引左边(不包括索引位置处)的元素保留在原vec中,索引右边(包括索引位置处)的元素在返回的vec

    更多的方法操作可以看:https://www.rustwiki.org.cn/zh-CN/std/vec/struct.Vec.html

    二. 字符串

    Rust中的字符串使用了UTF-8编码。Rust在语言核心部分只有一种字符串类型,那就是字符串切片(str),它通常以借用(&str)的形式出现。

    字符串是比很多开发者所理解的更为复杂的数据结构。加上UTF-8的不定长编码等原因,Rust 中的字符串并不如其它语言中那么好理解。

    2.1. 字符串创建

    基本上我们可以使用:newto_string方法进行创建:

    let a = "hello world"; // &str
    let b = a.to_string(); // String
    let c = String::from("hello world"); // String
    let d = "hello world".to_string(); // String
    
    • 1
    • 2
    • 3
    • 4

    2.2. 更新字符串

    更新字符串有三种方法,一种是使用push方法,如下:

    let mut a = "hello world".to_string();
    a.push_str(" rust");
    println!("{}", a); // hello world rust
    a.push('6');
    println!("{}", a); // hello world rust6
    
    • 1
    • 2
    • 3
    • 4
    • 5

    另一种是使用+运算符,这里可以看作是push_str函数

    let s1 = String::from("hello ");
    let s2 = String::from("world");
    let s3 = s1 + &s2;
    println!("{}", s3); // hello world
    
    • 1
    • 2
    • 3
    • 4

    最后一种是使用format!宏:

    let s1 = String::from("hello ");
    let s2 = String::from("world");
    let s3 = format!("{}{}", s1, s2);
    println!("{}", s3); // hello world
    
    • 1
    • 2
    • 3
    • 4

    2.3. 其他操作

    字符串索引,我们下面先看一个例子:

    let s1 = String::from("我爱祖国");
    println!("{}", s1[1]); // error[E0277]: the type `String` cannot be indexed by `{integer}`
    
    • 1
    • 2

    上面报错的原因是因为,UTF-8 是不定长编码,但是String的实现是基于 Vec 的封装的,数组中每一个元素都是一个字节,但UTF-8中每一个汉字(或字符)都可能由一到四个字节组成。接着我们看一下字符串分隔操作:

    let s1 = String::from("hello world");
    println!("{}", &s1[0..3]); // hel
    
    • 1
    • 2

    最后看一下字符串遍历操作:

    let s1 = String::from("hello world");
    for char in s1.chars() {
        println!("{}", char)
    }
    
    • 1
    • 2
    • 3
    • 4

    2.4. 常用方法

    更多的操作可以标准库文档:https://www.rustwiki.org.cn/zh-CN/std/string/struct.String.html

    方法描述
    new()创建一个新的字符串对象
    to_string()将字符串字面量转换为字符串对象
    replace()搜索并替换
    as_str()提取包含整个 String 的字符串切片
    push()将给定的 char 追加到该 String 的末尾
    push_str()将给定的字符串切片追加到这个 String 的末尾
    pop()从字符串缓冲区中删除最后一个字符并返回它
    remove()从该 String 的字节位置删除 char 并将其返回
    insert()在此 String 的字节位置插入一个字符
    len()返回此 String 的长度,以字节为单位
    is_empty()如果此 String 的长度为零,则返回 true,否则返回 false
    clear()截断此 String,删除所有内容

    三. 哈希映射

    哈希映射,存储了K类型键到V类型值之间的映射关系。这个我们在Java、go里面也有这种数据类型。

    具体操作可以看标准库文档:https://www.rustwiki.org.cn/zh-CN/std/collections/struct.HashMap.html

    3.1. 创建哈希映射

    下面演示使用new创建了一个空的HashMap,使用insertHashMap中添加元素。

    let mut map = HashMap::new();
    // let mut map = HashMap::with_capacity(10); // 设置指定容量的HashMap
    map.insert(String::from("Blue"), 10);
    map.insert(String::from("Red"), 100);
    
    • 1
    • 2
    • 3
    • 4

    RustHashMap还提供form/into方法可以用来创建存在初始值的HashMap

    let map1 = HashMap::from([
        ("a", 1),
        ("b", 2),
        ("c", 3),
    ]);
    // 使用into方法需要指定类型,此时编译器会推导出map2的类型是HashMap<_: &str, _: i32>
    let map2: HashMap<_, _> = [("a", 1), ("b", 2), ("c", 3), ].into();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    另外也可以通过entry函数进行插入:

    let mut map = HashMap::new();
    let text = "hello world rust";
    for ch in text.chars(){ 
        // 判断字符是否存在,如果不存在则将初始化数量为0, 此时counter的类型是&mut i32
        let counter = map.entry(ch).or_insert(0);
        // 存在的话增加数量 *counter是解引用
        *counter += 1;
    }
    println!("{:?}", map);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3.2. 访问和更新

    Rust中哈希映射提供了多种方法访问哈希映射的键和值:

    let mut map = HashMap::from([("a", 1), ("b", 2), ("c", 3)]);
    // 访问key
    map.keys().for_each(|key| {
        println!("{}", key)
    });
    // 访问value
    map.values().for_each(|value| {
        println!("{}", value)
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    获取value的方法也可以通过get等方法,这些方法返回的都是Option类型

    map.get("a").map(|data| { println!("{}", data) }); 
    map.get_mut("a").map(|data| { *data = 10 }); // get_mut可以获取可变引用
    map.get_key_value("a").map(|(key, value)| {
        println!("key: {}, value: {}", key.deref(), value)
    }); // get_key_value 返回与提供的键相对应的键值
    
    • 1
    • 2
    • 3
    • 4
    • 5

    接着我们看一个删除/过滤的操作,

    let mut map = HashMap::new();
    let text = "hello world rust";
    for ch in text.chars(){
        let counter = map.entry(ch).or_insert(0);
        *counter += 1;
    }
    // 删除前 => {'h': 1, 'l': 3, 'r': 2, 'o': 2, ' ': 2, 'd': 1, 't': 1, 'u': 1, 's': 1, 'e': 1, 'w': 1}
    println!("删除前 => {:?}", map);
    map.retain(|&k, v| { k != ' ' && *v > 1 });
    // 删除后 => {'l': 3, 'r': 2, 'o': 2}
    println!("删除后 => {:?}", map);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.3. 常用方法

    方法描述
    capacity()获取容量
    clear()清空
    contains_key()是否包含key
    drain()清除map,将所有键值对作为迭代器返回
    drain_filter()
    entry()map中获取给定键的对应项,以进行就地操作。
    get/get_mut/get_key_value通过某一个key获取value
    insert()插入
    into_keys()创建一个消费迭代器,以任意顺序访问所有键。 调用后不能使用map
    into_values()创建一个消费迭代器,以任意顺序访问所有值。 调用后不能使用map
    is_empty()是否为空
    keys()获取所有的key
    len获取长度
    remove()根据某一个key删除
    remove_entry()map中删除一个键,如果该键以前在map中,则返回存储的键和值
    retain()删除指定谓词的元素,可以看着条件过滤
  • 相关阅读:
    MFC Windows 程序设计[310]之混搭个性按钮组群(附源码)
    UE4_材质_湿度着色器及Desaturation算法_ben材质教程
    达梦数据库 优化
    【Spring Boot】Spring Boot源码解读与原理剖析
    linux
    C++基础学习01
    总结MySQL 的一些知识点:MySQL 导出数据
    速锐得解码新款坦克300网关(Gateway)采集CAN总线数据实操过程
    数据库管理-第四十二期 复盘一下(20221104)
    还在为如何编写Web自动化测试用例而烦恼嘛?资深测试工程师手把手教你Selenium 测试用例编写
  • 原文地址:https://blog.csdn.net/yhflyl/article/details/128105195