当前位置: 首页 > 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.dtcms.com/a/336938.html

相关文章:

  • Linux网络服务(一)——计算机网络参考模型与子网划分
  • 计算机网络:2、TCP和UDP
  • Golang context
  • CentOS 7 LAMP快速部署WordPress指南
  • 云原生Ansible渗透场景(⾃动化的运维⼯具)
  • Ansible企业及实战
  • OVS:除了Geneve和VXLAN,还有哪些虚拟化网络协议?
  • 云计算:企业数字化转型的核心引擎
  • 传统方式部署(RuoYi-Cloud)微服务
  • 一套GoldenGate → Kafka → Flink → MySQL 的端到端增量同步方案
  • 云计算学习100天-第17天
  • Linux学习-(进程间,线程间通信)
  • nuScence数据集
  • 计算机视觉 图像处理 在两张二值图中检测线条交集点的高效方法 适合工程图纸比对、生物神经元网络分析和文档特征提取等场景 ,
  • 20. 云计算-Service MeshServerless
  • 谷粒商城项目-P3简介-分布式基础概念
  • CloudBase AI ToolKit + VSCode Copilot:打造高效智能云端开发新体验
  • 【运维进阶】LNMP + WordPress 自动化部署实验
  • CMakeLists.txt 学习笔记
  • MariaDB/MySQL 客户端工具与服务端配置精要指南
  • C++---有符号和无符号整数的位移操作
  • 云原生俱乐部-mysql知识点归纳(1)
  • 《亚矩阵云手机重构出租接单:KVM 虚拟化与边缘计算驱动的设备替代技术路径》
  • 8.18决策树
  • 性能测试(Jemter)
  • grep命令要点、详解和示例
  • 基于nvm安装管理多个node.js版本切换使用(附上详细安装使用图文教程+nvm命令大全)
  • QT第九讲- 控件委托
  • Git智能合并机制深度解析
  • ChatGPT-5 对教育行业的影响与案例研究