Rust常见集合

2023-09-19 12:32:36

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

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

vector

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

vector的创建

新建一个空的vector:

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

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

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

增加元素

向vector增加元素:

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

    v.push(5);
    v.push(6);
    v.push(7);
    v.push(8);
}

访问元素

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

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    let third: &i32 = &v[2];
    println!("The third element is {third}");

    let third: Option<&i32> = v.get(2);
    match third {
        Some(third) => println!("The third element is {third}"),
        None => println!("There is no third element."),
    }
}

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

遍历元素

使用for in遍历vector:

fn main() {
    let v = vec![100, 32, 57];
    for i in &v {
        println!("{i}");
    }
}

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

fn main() {
    let mut v = vec![100, 32, 57];
    for i in &mut v {
        *i += 50;
    }
}

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

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

元素的生命周期

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

string

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

创建字符串

新建一个空字符串:

let mut s = String::new();

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

fn main() {
    let data = "initial contents";

    let s = data.to_string();

    // 该方法也可直接用于字符串字面值:
    let s = "initial contents".to_string();
}

从字符串字面值创建:

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

字符串或者字符的附加

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

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

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

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

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

fn main() {
    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");

    let s = s1 + "-" + &s2 + "-" + &s3;
}

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

fn main() {
    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");

    let s = format!("{s1}-{s2}-{s3}");
}

不支持索引

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

创建字符串slice

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

#![allow(unused)]
fn main() {
let hello = "Здравствуйте";

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

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

遍历字符串

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

#![allow(unused)]
fn main() {
for c in "Зд".chars() {
    println!("{c}");
}
}

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

#![allow(unused)]
fn main() {
for b in "Зд".bytes() {
    println!("{b}");
}
}

HashMap

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

创建HashMap

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

fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);
}

访问值

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

fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    let team_name = String::from("Blue");
    let score = scores.get(&team_name).copied().unwrap_or(0);
}

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

遍历HashMap

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

fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();

    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Yellow"), 50);

    for (key, value) in &scores {
        println!("{key}: {value}");
    }
}

HashMap和所有权

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

fn main() {
    use std::collections::HashMap;

    let field_name = String::from("Favorite color");
    let field_value = String::from("Blue");

    let mut map = HashMap::new();
    map.insert(field_name, field_value);
    // 这里 field_name 和 field_value 不再有效,
    // 尝试使用它们看看会出现什么编译错误!
}

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

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

更新值

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

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

fn main() {
    use std::collections::HashMap;

    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);

    scores.entry(String::from("Yellow")).or_insert(50);
    scores.entry(String::from("Blue")).or_insert(50);

    println!("{:?}", scores);
}

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

fn main() {
    use std::collections::HashMap;

    let text = "hello world wonderful world";

    let mut map = HashMap::new();

    for word in text.split_whitespace() {
        let count = map.entry(word).or_insert(0);
        *count += 1;
    }

    println!("{:?}", map);
}

参考:

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

更多推荐

Mysql库的操作

文章目录1.库的操作2.字符集和校验规则2.1校验规则对数据库的影响2.2修改数据库2.3备份和恢复3.查看连接情况1.库的操作说明:大写的表示关键字[]是可选项CHARACTERSET:指定数据库采用的字符集COLLATE:指定数据库字符集的校验规则这就是创建一个最简单的数据库。如果我们想看这个数据库的基本信息,我们

「计算机知识随记」Unicode 与 UTF-8 和仍然存在的乱码问题

0背景中文维基百科对Unicode的定义是:如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。可是在Unicode已经发布的(Unicode1991年发布,UTF-8是1993年初发布)三十年中,编

九、性能测试之网络测试

性能测试之网络测试一、网络知识二、判断带宽是否有问题1)查看本机带宽:1、ping目标服务器2、查看网卡三、网络调优实战:当接口报地址被占用/连接超时原因1:是源端口不够用解决方案:1、我们尝试去掉keep-alive,发现没有根本解决问题,只是把报错时间延后2、在windows和linux解决源端口的问题1)wind

开启海外“新副本”,中旭未来有几道“关卡”要闯?

随着游戏版号逐渐恢复正常发行,我国游戏市场重回增长快车道,行业景气度也逐步回升。伽马数据显示,2023年1-6月,中国自主研发游戏国内市场实际销售收入1217.84亿元,环比增长24.53%。近期,凭一句“我是渣渣辉,是兄弟就来砍我”走红全网的“贪玩蓝月”系列游戏迎来好消息,其母公司广州中旭未来科技有限公司(以下简称“

iMovie for Mac v10.3.9(视频剪辑)

iMovie是一款视频剪辑软件,广泛应用于Mac和iOS设备。以下是关于iMovie软件的一些推荐信息:简单易用。iMovie的设计简洁,操作简单,即使是没有剪辑经验的新手也可以轻松上手。软件内置了丰富的视觉效果、滤镜、绿幕抠图、分屏和画中画功能,可以满足用户的基本需求。高质量音频编辑工具。iMovie具有强大的音频编

nlp自然语言处理

NLPnlp自然语言处理(不一定是文本,图形也可以)接入深度学习(向量处理),需要把文字等内容转换成向量输入深度学习分为有监督和无监督学习两类,对应分类和生成算法都是向量输入词嵌入(映射到向量)词嵌入最简单的模型是one-hot,但数据计算量太大,所以后续更多的是减少数据量和建立关联性one-hot,最简单分类(单位矩

5G技术与教育的融合:挑战与机遇

5G技术与教育的融合:挑战与机遇摘要:本文旨在探讨5G技术在教育领域的应用及其对教育产业的潜在影响。首先,我们将简要介绍5G技术和现代教育技术的背景和现状。接着,我们将详细讲解5G技术在教育领域的应用,包括教学场景、互动模式和教育内容等方面,并通过实际案例进行分析。随后,我们将对5G技术在教育中的优缺点进行深入探讨,提

解锁前端Vue3宝藏级资料 第五章 Vue 组件应用 1( Props )

本章带领大家理解组件、props、emits、slots、providers/injects,Vue插件等Vue组件使用的基础知识。5.1组件注册5.2Props5.2.1组件之间如何传值5.2.2参数绑定v-bind5.2.3参数类型5.2.4props默认与必填5.2.5验证设置5.2.6useAttrs属性设置第

大数据名词——MPP(Massively Parallel Processing)数据集市

MPP(MassivelyParallelProcessing)数据集市是指一种基于大规模并行处理的数据存储和分析平台,旨在支持高效的数据处理和查询。MPP数据集市通常由多个节点组成,每个节点都具备计算和存储能力,并且可以同时处理大量的数据。MPP数据集市的主要特点包括以下几个方面:1.并行处理能力:MPP数据集市使用

知识产权之围:跨境电商卖家的法律防线

在当今数字化全球市场中,跨境电商是企业扩大国际业务的重要途径。然而,随着全球贸易的复杂化,知识产权问题已成为跨境电商卖家必须面对的挑战之一。本文将通过一个具体案例来探讨知识产权的重要性以及跨境电商卖家如何建立法律防线来保护自己的创意和品牌。Sophia'sBoutique的知识产权之困Sophia是一位富有创造力的跨境

Springboot项目升级2.2.x升至2.7.x

依赖管理spring-boot-starter-parent升级为2.7.1<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><!--升级为2.7.x的版本--><v

热文推荐