当前位置: 首页 > news >正文

Rust学习笔记(六)|Rust 中的常用集合(Vector、String、HashMap)

本篇文章包含的内容

  • 1 Vector
    • 1.1 定义 Vector
    • 1.2 访问 Vector中的元素
    • 1.3 遍历 Vector
    • 1.4 使用 Vector 和 Enum 配合存放多种数据
  • 2 String
    • 2.1 定义 String
    • 2.2 更新 String
    • 2.3 访问 String 的一部分
  • 3 HashMap
    • 3.1 定义 HashMap
    • 3.2 HashMap 的所有权
    • 3.3 访问 HashMap
    • 3.4 更新 HashMap
    • 3.5 Hash 函数


Rust标准库提供了一些常用的集合,例如Vector、String和HashMap,这些集合的共同特点是数据存储在Heap(堆)内存上,可以在运行时动态地确定他们的大小。

1 Vector

1.1 定义 Vector

Vector由Rust标准库提供,写法为Vec<T>,允许在内存中存放多个相同类型的数据,这些数据在内存中连续存放。使用下面的方法创建一个Vector。

let v1: Vec<i32> = Vec::new();	// 编译器无法自动推断时显式指定Vector的类型let mut v2 = Vec::new();
v2.push(1);		// 编译器自动推断v2类型let v3 = vec![1, 2, 3];		// 使用vec!宏

Vector离开作用域时数据就会被清理(Drop)。

1.2 访问 Vector中的元素

使用下面两种方式读取Vector中的元素:

  • 使用索引:访问越界时程序会panic
  • get方法:返回Option枚举,访问越界时程序会返回None
fn main() {let v = vec![1, 2, 3, 4, 5];let third: &i32 = &v[2];println!("The third element is {}", third);match v.get(2) {Some(third) => println!("The third element is {}", third),None => println!("There is no element!"),}
}

之前在介绍所有权时了解过,一个变量不可以同时拥有可变的引用和不可变的引用。这一特性对Vector也有效,即使只引用了Vector中的一部分:

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

Vector在内存中是连续的,如果允许在引用部分元素时在末尾添加元素,那么添加元素时就可能重新分配内存,这样就可能导致原来数据的位置发生改变,所以Rust直接杜绝了这种行为。

1.3 遍历 Vector

通常使用for循环遍历Vector:

fn main() {let mut v = vec![100, 50, 150];for i in &v {println!("{}", i);}for i in &mut v {*i += 50;       // 解引用println!("{}", i);}
}

在这里插入图片描述

1.4 使用 Vector 和 Enum 配合存放多种数据

在之前介绍枚举时,我们了解到可以使用枚举嵌入数据的方式定义枚举,既然枚举是确定的一种类型,那么就可以将其放入Vector中。

enum SpreadsheetCell {Int(i32),Float(f64),Text(String),
}fn main() {let row = vec![SpreadsheetCell::Int(3),SpreadsheetCell::Text(String::from("blue")),SpreadsheetCell::Float(10.12),];
}

注意,这种方法只适用于Vector中“存放”的可能的数据类型是详尽已知的,如果数据类型有无限种可能,那么枚举也无法定义。这时候可以使用Trait对象来解决,这种方法以后再了解。

2 String

在Rust中,字符串是由标准库提供的基于字节组织的集合,并提供了一系列方法使得我们可以解码字符串的内容。Rust核心代码层面字符串指&str,在标准库层面又提供了StringString&str都采用UTF-8编码,其中存放的字符都是Unicode字符,它不仅支持汉字,甚至支持阿拉伯语、梵文等特殊字符的解码。标准库中还提供了其他的字符串类型,例如OsStringOsStrCStringCStr,但是这里不作细致讨论,入门时仅了解String即可。

2.1 定义 String

定义String可以使用to_string()方法或者String::from()函数:

fn main() {let s1 = "initial string".to_string();		// 适用所有实现了Display方法的类型let s2 = 123.to_string();let s3 = String::from("hello");let mut s4 = String::new();		// 创建一个空 String
}

2.2 更新 String

可以对String进行添加或者拼接操作:

fn main() {let mut s1 = "initial string".to_string();s1.push_str(" foo");    // 添加一个&strs1.push('b');       // 添加一个字符println!("{}", s1);
}

特别地,可以使用+对字符串进行拼接操作,+前后的数据类型如下所示,+前的String所有权会发生丢失(所有权移入add函数):

fn main() {let s1 = String::from("hello");let s2 = String::from(" world");let s3 = s1 + &s2;println!("{}", s3);// println!("{}", s1);     // s1 所有权丢失println!("{}", s2);
}

使用format!()宏可以更加方便得拼接字符串,并且这种方法不会获得任何变量的所有权:

fn main() {let s1 = String::from("tic");let s2 = String::from("tac");let s3 = String::from("toe");let s = format!("{}-{}-{}", s1, s2, s3);    // 不会获得s1, s2, s3的所有权println!("{}", s);
}

2.3 访问 String 的一部分

Rust不支持使用索引访问String类型的部分元素。String本质是对Vec<u8>的包装,使用len()方法可以获得该String占用的字节数。

如果要访问字符串的一部分,可以使用之前学习过的字符串切片&str,但是切片时必须保证切片的位置是字符的边界,如果切片位置不是字符边界,程序就会panic

Rust有三种看待字符的方式:字节、Unicode标量值、字形簇,其中字形簇是最接近我们理解字符的方式。但是获取字形簇相对较为复杂,标准库没有实现遍历它的方法,这里仅作了解即可。

fn main() {let s = "你好";for b in s.bytes() {	// 遍历字节println!("{}", b);}for b in s.chars() {	// 遍历Unicode标量值println!("{}", b);}
}

在这里插入图片描述

3 HashMap

3.1 定义 HashMap

HashMap是一种通过键值对组织数据的数据类型,类型为HashMap<K, V>,其中KV都是确定的类型。一个HashMap中所有K必须是同一种类型,所有V也必须是同一种类型(同构)。它内部存在一个Hash函数,描述了如何在内存中存储数据。

这种数据结构有点像Python中的字典。HashMap并没有预导入,需要使用use关键字对它进行导入,使用下面的方法定义一个HashMap:

use std::collections::HashMap;fn main() {let mut scores = HashMap::new();scores.insert("blue", 50);   // 类型自动推断
}

另一种常用的创建HashMap的方法是使用collect方法,它使用元组Tuple进行创建。collect方法科技把数据整合成多种数据类型,包括HashMap,所以在创建时需要显式指明返回值的类型。

use std::collections::HashMap;fn main() {let teams = vec![String::from("Blue"), String::from("Yellow")];let initial_scores = vec![10, 50];let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();  // 两个迭代器通过zip方法返回一个元组
}

3.2 HashMap 的所有权

对于实现了Copy Trait的数据,值会被复制到HashMap中;对于拥有所有权的类型,值会发生移动(Move),所有权会转移给HashMap,除非使用了引用。使用引用初始化时,数据也只有一个,所以在作用域内必须保证数据一直有效。

use std::collections::HashMap;fn main() {let field_name = String::from("Favorite color");let field_value = String::from("Blue");let mut map = HashMap::new();// map.insert(field_name, field_value);     // 初始化时所有权会发生转移map.insert(&field_name, &field_value);println!("{}: {}", field_name, field_value);
}

3.3 访问 HashMap

使用get方法访问HashMap中的元素,get方法会返回一个Option枚举,所以就很适合使用match语句。

use std::collections::HashMap;fn main() {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);match score {None => println!("Team not exist"),Some(s) => println!("{}", s),}
}

也可以使用for语句遍历HashMap

use std::collections::HashMap;fn main() {let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Yellow"), 50);for (k, v) in &scores {		// 模式匹配println!("{}: {}", k, v);}
}

3.4 更新 HashMap

在更新HashMap时,会出现以下几种情况,对每一种情况Rust标准库都内置了方案来方便地解决:

  • K不存在:将新的K和V添加进HashMap
  • K存在:
    • 替换现有的V
    • 保留现有的V,忽略新的V
    • 合并现有的和新的V

替换现有的V:如果向HashMap插入一对KV,然后再插入相同的K,但是V不同,这时新的V就会替换掉现有的V。

use std::collections::HashMap;fn main() {let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.insert(String::from("Blue"), 50);println!("{:?}", scores);	// {"Blue": 50}
}

只有插入的K不存在才会执行插入:使用entry方法。entry方法会返回一个Entry枚举,通过这个枚举判断K是否存在,配合or_insert方法将K不存在时的新的KV插入HashMap,如果K存在则不执行任何操作:

use std::collections::HashMap;fn main() {let mut scores = HashMap::new();scores.insert(String::from("Blue"), 10);scores.entry(String::from("Yellow")).or_insert(20);scores.entry(String::from("Blue")).or_insert(50);println!("{:?}", scores);   // {"Yellow": 20, "Blue": 10}
}

基于现有V来更新V:or_insert方法总会返回V的可变引用,如果K存在,则返回K对应的V的可变引用;如果K不存在,则返回新插入的值的可变引用。

use std::collections::HashMap;fn main() {let text = "hello world wonderful hello";let mut scores = HashMap::new();for item in text.split_whitespace() {let count = scores.entry(item).or_insert(0);*count += 1;}println!("{:?}", scores);   // {"hello": 2, "world": 1, "wonderful": 1}
}

3.5 Hash 函数

默认情况下,HashMap使用加密功能强大的Hash函数,可以抵抗拒绝服务(Dos)攻击,它不是最快的Hash算法,但是拥有较好的安全性。

可以指定不同的hasher来切换Hash函数,hasher是实现BuildHasher trait的类型。这里仅作了解即可。


  原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、FPGA方面的学习笔记。


http://www.lryc.cn/news/624253.html

相关文章:

  • MiniMax Agent 上线 Market Place ,AI一键复制克隆网站
  • 部署 HAProxy 高可用
  • python 数据拟合(线性拟合、多项式回归)
  • Android Coil3视频封面抽取封面帧存Disk缓存,Kotlin(2)
  • 云计算:企业数字化转型的核心引擎
  • Kubernetes(K8s)常用命令全解析:从基础到进阶
  • 【Kubernetes】在 K8s 上部署 Prometheus
  • C语言基础:变量与进制详解
  • K8s的命名空间需要创建吗
  • 工具集成强化学习:AI数学推理能力的新跃迁
  • Java基础(九):Object核心类深度剖析
  • 图神经网络分享系列-node2vec(二)
  • 基于51单片机WIFI心率计脉搏体温测量仪APP设计
  • HTML应用指南:利用POST请求获取全国华为旗舰店门店位置信息
  • 《若依》权限控制
  • 上下文切换及线程操作相关内容
  • 学习雪花算法
  • linux-高级IO(中)
  • 【BFS 动态规划】P12382 [蓝桥杯 2023 省 Python B] 树上选点|普及+
  • Redis面试精讲 Day 25:Redis实现分布式Session与购物车
  • 【前端】使用Vue3过程中遇到加载无效设置点击方法提示不存在的情况,原来是少加了一个属性
  • [激光原理与应用-296]:理论 - 非线性光学 - 线性光学与非线性光学对比
  • (第十九期)用 VS Code 管理项目:目录文件夹与根目录,一次讲清
  • Vulkan笔记(五)-逻辑层与队列
  • halcon基于透视的可变形模型匹配
  • C预备知识01:
  • 数字电视:技术演进与未来展望
  • 用户认证技术
  • MySQL 函数大赏:聚合、日期、字符串等函数剖析
  • 静配中心配药智能化:基于高并发架构的Go语言实现