【Rust基础④】Rust中的集合类型(Vector与HashMap)

文章目录

  • 8 集合类型
    • 8.1 动态数组 Vector
      • 8.1.1 创建动态数组
      • 8.1.2 从 Vector 中读取元素
      • 8.1.3 迭代遍历 Vector 中的元素
      • 8.1.4 存储不同类型的元素
    • 8.2 KV 存储 HashMap
      • 8.2.1 创建 HashMap
        • 使用 new 方法创建
        • 使用迭代器和 collect 方法创建
      • 8.2.2 查询 HashMap
      • 8.2.3 更新 HashMap 的值
      • 8.2.4 哈希函数
  • 9 类型转换
    • 9.1 as 转换
    • 9.2 TryInto 转换
    • 9.3 通用类型转换

8 集合类型

8.1 动态数组 Vector

动态数组允许你存储多个值,这些值在内存中一个紧挨着另一个排列,因此访问其中某个元素的成本非常低。

8.1.1 创建动态数组

let mut v1 = Vec::new(); 
v.push(1); //自动推导类型为Vec<i32>let v2 = vec![1, 2, 3];

跟结构体一样,Vector 类型在超出作用域范围后,会被自动删除

8.1.2 从 Vector 中读取元素

读取指定位置的元素有两种方式可选:

  • 通过下标索引访问。
  • 使用 get 方法。
let v = vec![1, 2, 3, 4, 5];let third: &i32 = &v[2];
println!("第三个元素是 {}", third);match v.get(2) {Some(third) => println!("第三个元素是 {}", third),None => println!("去你的第三个元素,根本没有!"),
}

若数组访问越界,v.get 在内部做了处理,有值的时候返回 Some(T),无值的时候返回 None,因此 v.get 的使用方式非常安全。

let mut v = vec![1, 2, 3, 4, 5];let first = &v[0]; //不可变借用v.push(6); //可变借用println!("The first element is: {}", first);

编译上面的代码,编译器会报错:

$ cargo run
Compiling collections v0.1.0 (file:///projects/collections)
error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable 无法对v进行可变借用,因此之前已经进行了不可变借用
--> src/main.rs:6:5
|
4 |     let first = &v[0];
|                  - immutable borrow occurs here // 不可变借用发生在此处
5 |
6 |     v.push(6);
|     ^^^^^^^^^ mutable borrow occurs here // 可变借用发生在此处
7 |
8 |     println!("The first element is: {}", first);
|                                          ----- immutable borrow later used here // 不可变借用在这里被使用For more information about this error, try `rustc --explain E0502`.
error: could not compile `collections` due to previous error

原因在于:数组的大小是可变的,当旧数组的大小不够用时,Rust 会重新分配一块更大的内存空间,然后把旧数组拷贝过来。这种情况下,之前的引用显然会指向一块无效的内存,这非常 rusty —— 对用户进行严格的教育。

8.1.3 迭代遍历 Vector 中的元素

如果想要依次访问数组中的元素,可以使用迭代的方式去遍历数组,这种方式比用下标的方式去遍历数组更安全也更高效(每次下标访问都会触发数组边界检查):

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

也可以在迭代过程中,修改 Vector 中的元素:

let mut v = vec![1, 2, 3];
for i in &mut v {*i += 10
}

8.1.4 存储不同类型的元素

通过枚举实现:

#[derive(Debug)]
enum IpAddr {V4(String),V6(String)
}
fn main() {let v = vec![IpAddr::V4("127.0.0.1".to_string()),IpAddr::V6("::1".to_string())];for ip in v {show_addr(ip)}
}fn show_addr(ip: IpAddr) {println!("{:?}",ip);
}

数组 v 中存储了两种不同的 ip 地址,但是这两种都属于 IpAddr 枚举类型的成员,因此可以存储在数组中。

特征对象的实现:

trait IpAddr {fn display(&self);
}struct V4(String);
impl IpAddr for V4 {fn display(&self) {println!("ipv4: {:?}",self.0)}
}
struct V6(String);
impl IpAddr for V6 {fn display(&self) {println!("ipv6: {:?}",self.0)}
}fn main() {let v: Vec<Box<dyn IpAddr>> = vec![Box::new(V4("127.0.0.1".to_string())),Box::new(V6("::1".to_string())),];for ip in v {ip.display();}
}

比枚举实现要稍微复杂一些,我们为 V4V6 都实现了特征 IpAddr,然后将它俩的实例用 Box::new 包裹后,存在了数组 v 中,需要注意的是,这里必须手动地指定类型:Vec<Box<dyn IpAddr>>,表示数组 v 存储的是特征 IpAddr 的对象,这样就实现了在数组中存储不同的类型。

在实际使用场景中,特征对象数组要比枚举数组常见很多,主要原因在于特征对象非常灵活,而编译器对枚举的限制较多,且无法动态增加类型。

8.2 KV 存储 HashMap

HashMap 中存储的是一一映射的 KV 键值对,并提供了平均复杂度为 O(1) 的查询方法,Rust 中哈希类型(哈希映射)为 HashMap<K,V>

8.2.1 创建 HashMap

使用 new 方法创建

使用 new 方法来创建 HashMap,然后通过 insert 方法插入键值对。

use std::collections::HashMap;// 创建一个HashMap,用于存储宝石种类和对应的数量
let mut my_gems = HashMap::new();// 将宝石类型和对应的数量写入表中
my_gems.insert("红宝石", 1);
my_gems.insert("蓝宝石", 2);
my_gems.insert("河边捡的误以为是宝石的破石头", 18);

HashMap 的类型:HashMap<&str,i32>。所有的集合类型都是动态的,意味着它们没有固定的内存大小,因此它们底层的数据都存储在内存堆上,然后通过一个存储在栈中的引用类型来访问。同时,跟其它集合类型一致,HashMap 也是内聚性的,即所有的 K 必须拥有同样的类型,V 也是如此。

Vec 一样,如果预先知道要存储的 KV 对个数,可以使用 HashMap::with_capacity(capacity) 创建指定大小的 HashMap,避免频繁的内存分配和拷贝,提升性能

使用迭代器和 collect 方法创建
fn main() {use std::collections::HashMap;let teams_list = vec![("中国队".to_string(), 100),("美国队".to_string(), 10),("日本队".to_string(), 50),];let teams_map: HashMap<_,_> = teams_list.into_iter().collect();println!("{:?}",teams_map)
}

into_iter 方法将列表转为迭代器,接着通过 collect 进行收集,不过需要注意的是,collect 方法在内部实际上支持生成多种类型的目标集合,因此我们需要通过类型标注 HashMap<_,_> 来让编译器自己推导具体的KV类型。

HashMap 的所有权规则与其它 Rust 类型没有区别:

  • 若类型实现 Copy 特征,该类型会被复制进 HashMap,因此无所谓所有权
  • 若没实现 Copy 特征,所有权将被转移给 HashMap

8.2.2 查询 HashMap

通过 get 方法可以获取元素:

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: Option<&i32> = scores.get(&team_name);

上面有几点需要注意:

  • get 方法返回一个 Option<&i32> 类型:当查询不到时,会返回一个 None,查询到时返回 Some(&i32)
  • &i32 是对 HashMap 中值的借用,如果不使用借用,可能会发生所有权的转移

还可以通过循环的方式依次遍历 KV 对:

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

8.2.3 更新 HashMap 的值

查询某个 key 对应的值,若不存在则插入新值,若存在则对已有的值进行更新,例如在文本中统计词语出现的次数:

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);

上面代码中,新建一个 map 用于保存词语出现的次数,插入一个词语时会进行判断:若之前没有插入过,则使用该词语作 Key,插入次数 0 作为 Value,若之前插入过则取出之前统计的该词语出现的次数,对其加一。

有两点值得注意:

  • or_insert 返回了 &mut v 引用,因此可以通过该可变引用直接修改 map 中对应的值
  • 使用 count 引用时,需要先进行解引用 *count,否则会出现类型不匹配

8.2.4 哈希函数

一个类型能否作为 Key 的关键就是是否能进行相等比较,或者说该类型是否实现了 std::cmp::Eq 特征。

f32 和 f64 浮点数,没有实现 std::cmp::Eq 特征,因此不可以用作 HashMapKey

通过哈希函数可以把 Key 计算后映射为哈希值,然后使用该哈希值来进行存储、查询、比较等操作。

因此若性能测试显示当前标准库默认的哈希函数不能满足你的性能需求,就需要去 crates.io 上寻找其它的哈希函数实现

9 类型转换

9.1 as 转换

转换时,把范围较小的类型转换为较大的类型,下面列出了常用的转换形式:

fn main() {let a = 3.1 as i8;let b = 100_i8 as i32;let c = 'a' as u8; // 将字符'a'转换为整数,97println!("{},{},{}",a,b,c)
}

内存地址转换为指针:

let mut values: [i32; 2] = [1, 2];
let p1: *mut i32 = values.as_mut_ptr();
let first_address = p1 as usize; // 将p1内存地址转换为一个整数
let second_address = first_address + 4; // 4 == std::mem::size_of::<i32>(),i32类型占用4个字节,因此将内存地址 + 4
let p2 = second_address as *mut i32; // 访问该地址指向的下一个整数p2
unsafe {*p2 += 1;
}
assert_eq!(values[1], 3);

9.2 TryInto 转换

try_into 会尝试进行一次转换,并返回一个 Result,此时就可以对其进行相应的错误处理。

try_into 转换会捕获大类型向小类型转换时导致的溢出错误:

fn main() {let b: i16 = 1500;let b_: u8 = match b.try_into() {Ok(b1) => b1,Err(e) => {println!("{:?}", e.to_string());0}};
}

运行后输出如下 "out of range integral type conversion attempted",在这里我们程序捕获了错误,编译器告诉我们类型范围超出的转换是不被允许的,因为我们试图把 1500_i16 转换为 u8 类型,后者明显不足以承载这么大的值。

9.3 通用类型转换

在某些情况下,类型是可以进行隐式强制转换的,虽然这些转换弱化了 Rust 的类型系统,但是它们的存在是为了让 Rust 在大多数场景可以工作,而不是报各种类型上的编译错误。

在匹配特征时,不会做任何强制转换(除了方法)。一个类型 T 可以强制转换为 U,不代表 impl T 可以强制转换为 impl U

点操作符在调用时,会发生很多魔法般的类型转换,例如:自动引用、自动解引用,强制类型转换直到类型能匹配等。

假设有一个方法 foo,它有一个接收器(接收器就是 self&self&mut self 参数)。如果调用 value.foo(),编译器在调用 foo 之前,需要决定到底使用哪个 Self 类型来调用。现在假设 value 拥有类型 T

再进一步,我们使用完全限定语法来进行准确的函数调用:

  1. 首先,编译器检查它是否可以直接调用 T::foo(value),称之为值方法调用
  2. 如果上一步调用无法完成(例如方法类型错误或者特征没有针对 Self 进行实现,上文提到过特征不能进行强制转换),那么编译器会尝试增加自动引用,例如会尝试以下调用: <&T>::foo(value)<&mut T>::foo(value),称之为引用方法调用
  3. 若上面两个方法依然不工作,编译器会试着解引用 T ,然后再进行尝试。这里使用了 Deref 特征 —— 若 T: Deref<Target = U> (T 可以被解引用为 U),那么编译器会使用 U 类型进行尝试,称之为解引用方法调用
  4. T 不能被解引用,且 T 是一个定长类型(在编译器类型长度是已知的),那么编译器也会尝试将 T 从定长类型转为不定长类型,例如将 [i32; 2] 转为 [i32]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/110456.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【财政金融】全国各地区财政收入与支出面板数据合集(2000-2022年)

数据简介&#xff1a;2000年到2022年中国经历了快速的经济发展和城市化进程&#xff0c;各地区的财政收入和支出也呈现出显著的增长和变化&#xff0c;全国各地区财政收入与支出的面板数据可用于评估经济发展水平和区域差距&#xff0c;通过比较不同地区之间的财政收入和支出水…

来来来,降温啦给女儿这么穿,好看又保暖

家人们谁懂啊&#xff01;&#xff01;&#xff01; 时尚百搭有气质的羽绒服 小编强烈推荐哦&#xff01; 防风保暖设计加三防工艺 简直不要太哇塞 怎么穿都好看哦

Java设计模式之亨元模式(Flyweight Pattern)

亨元模式&#xff08;Flyweight Pattern&#xff09;是一种结构型设计模式&#xff0c;旨在通过共享对象来最大限度地减少内存使用和提高性能。该模式适用于需要创建大量相似对象的情况&#xff0c;其中许多对象具有相同的状态。通过共享相同的状态&#xff0c;亨元模式可以减少…

decltype关键字

获取表达式的类型 auto用于通过一个表达式在编译时确定待定义的变量类型&#xff0c;auto所修饰的变量必须被初始化&#xff0c;编译器需要通过初始化来确定auto所代表的类型&#xff0c;即必须要定义变量。若仅希望得到类型&#xff0c;而不需要&#xff08;或不能&#xff09…

机器人制作开源方案 | 行星探测车概述

1. 功能描述 行星探测车&#xff08;Planetary Rover&#xff09;是一种用于进行科学探索和勘测任务的无人车辆&#xff0c;它们被设计成能够适应各种复杂的地形条件和极端环境&#xff0c;以便收集数据、拍摄照片、采集样本等。行星探测车通常包含以下主要组件和功能&#xff…

抖音小程序制作源码系统 带完整搭建教程

在当今社交媒体时代&#xff0c;抖音作为一款备受欢迎的短视频应用&#xff0c;已经拥有了庞大的用户群体。与此同时&#xff0c;抖音小程序也成为了商家和开发者们关注的热点。今天小编就来给大家介绍一款抖音小程序制作源码系统&#xff0c;五分钟确实创建一个小程序。 系统特…

音乐播放器VHDL蜂鸣器数码管显示简谱,视频/代码

名称&#xff1a;音乐播放器数码管显示简谱蜂鸣器 软件&#xff1a;Quartus 语言&#xff1a;VHDL 代码功能&#xff1a; 设计音乐播放器&#xff0c;播放一首歌&#xff0c;使用开发板的蜂鸣器播放音乐&#xff0c;使用Quartus内的ROM IP核存储音乐文件&#xff0c;使用数…

为什么要做数据可视化

在当今信息爆炸的时代&#xff0c;数据已成为个人和企业最宝贵的资产之一。然而&#xff0c;仅仅拥有大量的数据并不足以支持明智的决策。数据可视化&#xff0c;作为一种将数据转化为图形形式的技术和方法&#xff0c;可以帮助我们更好地理解和分析数据&#xff0c;从而更准确…

《golang设计模式》第三部分·行为型模式-01-责任链模式(Chain of Responsibility)

文章目录 1 概念1.1 角色1.2 类图 2. 代码示例2.1 设计2.2 代码2.3 类图 1 概念 责任链&#xff08;Chain of Responsibility&#xff09;是指将客户端请求处理的不同职责对象组成请求处理链。 客户端只需要将请求交付到该链上&#xff0c;而不需要关心链上含有哪些对象。请求…

首次建站用香港服务器有影响没?

​  对于首次租用香港服务器的朋友来说&#xff0c;难免会对它没有一个很清晰的认知。因此&#xff0c;本文就从香港服务器适用人群&#xff0c;以及建站影响&#xff0c;选择技巧上做一个全方位的解答。 1. 哪一类人群适合使用香港服务器建站? 做外贸业务的网站。香港走的国…

时间序列预测 Graph-WaveNet:Graph WaveNet for Deep Spatial-Temporal Graph Modeling

Graph-WaveNet Graph WaveNet for Deep Spatial-Temporal Graph Modeling1.概述2.提出问题 & 解决策略 & 模型结构3.实验结果 ** Graph WaveNet for Deep Spatial-Temporal Graph Modeling ** 1.概述 时空图建模是分析系统中各组成部分的空间关系和时间趋势的一项重…

上海市教委产学研教师沙龙成功举办,共研大模型AI人才培养与插件生态

10月15日&#xff0c;百度飞桨携手上海市教育委员会、上海市学位委员会、上海物联网行业协会联合举办的“AI大模型产学研融合创新”上海研究生导师沙龙&#xff0c;在百度飞桨&#xff08;张江&#xff09;人工智能产业赋能中心顺利召开。来自上海地区20位高校及科研机构在聘研…

正向代理与反向代理

正向代理 客户端想要直接与目标服务器连接&#xff0c;但是无法直接进行连接&#xff0c;就需要先去访问中间的代理服务器&#xff0c;让代理服务器代替客户端去访问目标服务器 反向代理 屏蔽掉服务器的信息&#xff0c;经常用在多台服务器的分布式部署上&#xff0c;像一些大型…

C#快速排序算法

快速排序实现原理 快速排序&#xff08;Quick Sort&#xff09;是一种常用的排序算法&#xff0c;它基于分治的思想&#xff0c;通过将一个无序的序列分割成两个子序列&#xff0c;并递归地对子序列进行排序&#xff0c;最终完成整个序列的排序。 其基本思路如下&#xff1a; 选…

屏幕录制视频编辑软件 Camtasia 2023 mac中文版软件功能

Camtasia 2023 mac是一款功能强大的屏幕录制和视频编辑软件&#xff0c;可以用于制作教育课程、演示文稿、培训视频等。它具有一系列工具和功能&#xff0c;包括屏幕录制、视频编辑、音频编辑、字幕、特效等&#xff0c;使用户可以轻松地创建高质量的视频内容。 Camtasia2023的…

MongoDB索引操作

1、创建索引 语句&#xff1a; db.collection.createIndex(keys, options, commitQuorum) 选项参数名类型描述keys 包含排序字段和排序方式的对象&#xff0c; 值&#xff1a; 1为升序索引 -1为降序索引 options参数控制对象backgroundboolean 可选&#xff0…

计算机基础知识34

进程锁 # 锁在IT界很重要&#xff0c;不但在Python中出现&#xff0c;尤其是数据库中得锁更多&#xff0c;比如&#xff1a;表锁、行锁、 悲观锁、乐观锁、进程锁、互斥锁、递归锁、可重入锁、死锁等 # 保证安全 import time # 导入time&#xff0c;执行顺序乱了 from…

面试题-React(十五):React中的CSS编写方案及推荐

在React开发中&#xff0c;如何组织和编写CSS是一个常见的问题&#xff0c;React官方并没有一种固定的CSS编写方式&#xff0c;所以在目前的React开发中可能存在各种各样不同的编写CSS风格。本文将介绍React中几种常用的CSS编写方案&#xff0c;分析其优缺点&#xff0c;并推荐…

服务CPU异常飙高问题分析和解决

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是「奇点」&#xff0c;江湖人称 singularity。刚工作几年&#xff0c;想和大家一同进步&#x1f91d;&#x1f91d; 一位上进心十足的【Java ToB端大厂…

如何使用uview中的loadmore上拉加载

普通用法 HTML <view><view><!-- 内容 --></view><u-loadmore :status"status" /> </view> JS&#xff0c;onReachBottom这个是生命周期&#xff0c;和method同级 data() {return {goods:null,status: loadmore,//当前状态p…