Rust之通用集合类型

本文介绍了Rust编程语言中的三种主要集合数据结构:动态数组Vec,用于存储多个相同类型值;字符串,用于存储UTF-8编码文本;以及哈希映射HashMap,用于存储键值对。动态数组使用push方法添加元素,字符串可以使用push_str和format!宏进行拼接,哈希映射则提供了便捷的方式存储和访问键值对。所有这些数据结构都涉及到Rust的关键概念,如内存管理和所有权。
摘要由CSDN通过智能技术生成

在Rust语言中包含了一系列被称为集合的数据结构。大部分的数据结构都代表着某个特定的值,但集合却可以包含多个值。与内置的数组与元组类型不同,这些集合将自己持有的数据存储在了堆上。这意味着数据的大小不需要在编译时确定,并且可以随着程序的运行按需扩大或缩小数据占用的空间。
被广泛使用的3个Rust集合:

  • 动态数组:可以连续地存储任意多个值;
  • 字符串:字符的集合;
  • 哈希映射:可以将值关联到一个特定的键上,是映射的特殊实现。

1、使用动态数组存储多个值:

动态数组(Vec(T)),允许在单个数据结构中存储多个相同类型的值,这些值会彼此相邻地排布在内存中。

(1)、创建动态数组:

创建一个空的动态数组可以调用函数Vec::new
示例:

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

因为这是个空的动态数组,里面没有任何的值,所以Rust无法自动推导元素类型,就需要显式地增加一个类型标记。
在标准库中的Vec<T>是可以存储任意类型数据的,在实际的动态数组创建时,只要向数组内插入了数据,Rust便可以自行推导出想要存储的数据。Rust还提供了一个简化代码的宏vec!这个宏可以根据提供的数值来创建一个新的动态数组。
示例:

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

这里创建了含有初始值1,2,3的动态数组,Rust根据初始值推断出想要存储的数据类型为i32。

(2)、更新动态数组:

在创建好的动态数组中添加元素,需要使用push。示例:

let mut v = Vec::new();

v.push(5);
v.push(6);
v.push(19);

(3)、销毁动态数组时也会销毁其中的元素:

动态数组一旦离开作用域就会被立即销毁。示例:

{
	let v = vec![1,2,3];
	//执行其他操作
}//V在这里离开作用域并随之被销毁

动态数组中的所有内容会随着动态数组的销毁而销毁,其持有的整数将被自动清理干净。

(4)、读取动态数组中的元素:

访问动态数组中的数据,可以通过索引或者get方法。示例:

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 third element"),
}

注意:当使用索引值2获取数值时,获取的是第三个值,动态数组使用数字进行索引,索引值从零开始,使用&[]会直接返回元素的引用,而接收索引作为参数的get方法返回的则是一个Option<&T>。
同理,当越界访问元素时,直接使用[]会导致程序触发panic,而使用get方法时则会简单地返回None。
当一个变量持有了一个指向动态数组中首个元素的不可变引用,那么当向这个动态数组结尾添加元素时,会出现错误。示例:

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

这个错误是由动态数组的工作原理导致的:动态数组中的元素是连续存储的,插入新的元素后也许会没有足够的空间将所有的元素依次相邻地放下,这就需要分配一个新的内存空间,并将旧的元素移动到新的空间上去。但是当存在一个不可变引用,就会导致第一个元素的引用会因为插入新元素而指向被释放的内存上。

(5)、遍历动态数组中的值:

当需要依次访问动态数组的每一个元素的时候,可以直接遍历其所有的元素,而不需要使用索引一个一个地访问。示例:

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

同样也可以遍历可变的动态数组,获得元素的可变引用,并修改其中的值。示例:

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

为了使用+=运算符来修改可变引用指向的值,需要使用解引用运算符*来获得i绑定的值。

(6)、使用枚举来存储多个类型的值:

为了在动态数组中存储不同类型的元素,可以定义并使用枚举类型,因为枚举中的所有变体都被定义为了同一种枚举类型。示例:

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

为了计算出元素在堆上使用的存储空间,Rust需要在编译时确定动态数组的类型。使用枚举的另一个好处在于它可以显式地列举出所有可以被放入动态数组的值类型。假如Rust允许动态数组存储任意类型,那么在对动态数组中的元素进行操作时,就有可能会因为一个或多个不当的类型处理而导致错误。将枚举和match表达式搭配使用意味着,Rust可以在编译时确保所有可能的情形都得到妥当的处理。如果没有办法在编写程序时穷尽所有可能出现在动态数组中的值类型,那么就无法使用枚举。

2、使用字符串存储UTF-8编码的文本:

(1)、创建一个新的字符串:

可以使用new函数来创建一个新的字符串。示例:

let mut s = String::new();

这里创建了一个新的空字符串,之后可以将数据填入其中。
对含有初始数据的情况,在实现了Display trait的类型调用to_string方法去创建。示例:

let data = "initial contents";
let s = data.to_string();
//或者
let s = "initial contents".to_string();

同样也可以使用函数String::from来基于字符串字面量生成String。示例:

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

(2)、更新字符串:

String的大小可以增减,其中的内容也可以修改。正如将数据推入其中时Vec内部数据所发生的变化一样。此外,我们还可以方便地使用+运算符或format! 宏来拼接String。

i、使用push_str或push向字符串中添加内容:

push_str只需要接收一个字符串切片作为参数,并不需要获得参数的所有权。示例:

let mut s1 = String::from("foo");
let s2 = "bar";
s1.push_str(s2);
println!("s2 is {}", s2);

在执行完,依旧可以打印s2的值。
push方法接收单个字符作为参数,并将它添加到String中。示例:

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

ii、使用+运算符或format! 宏来拼接字符串:

如果需要将两个已经存在的字符串组合在一起,可以使用+运算符。示例:

let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2; // 注意这里的s1已经被移动且再也不能被使用了,s2仅被引用

这段程序看起来是将s1和s2两个字符串复制后创建一个新的字符串,实际上是获取了S1的所有权,再将S2复制进去,最后将S1的所有权作为结果返回。
如果需要拼接多个字符串,可以使用format!宏来实现。示例:

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}",s1,s2,s3);

format!宏会将结果包含在一个String中返回,并且不会夺取任何参数的所有权。

(3)、字符串索引:

在Rust中,字符串并不能向其他的编程语言那样通过索引去访问。
因为在Rust中,字符串是以UTF-8的形式存储的,每个字符都是由不定长的字节组成,所以通过常规的索引无法访问。

(4)、字符串切片:

如果需要一个字符串切片,则要在索引的[]中填写范围来指定所需要的字节内容,而不是在[]中填写一个单个数字。示例:

let hello = "3ΠpaB";
let s = &hello[0..4];

这段程序中,s是一个包含了字符串前4个字节的&str,如果每个字符都占据了两个字节,那么s中的内容就是3Π,如果使用&hello[0…1],那么程序就会报错。

3、在哈希映射中存储键值对:

哈希映射:HashMap<K,V>,存储了从K类型键到V类型值之间的映射关系。

(1)、创建一个新的哈希映射:

可以使用new来创建一个空的哈希映射,并通过insert方法来添加元素。示例:

use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"),10);
scores.insert(String::from("Yellow"),50);

这里的HashMap拥有类型为String的键,以及类型为i32的值。和动态数组一样,哈希映射也是同质的:它要求所有的键必须拥有相同的类型,所有的值也必须拥有相同的类型。
另一种构建哈希映射的方法是,在一个由键值对组成的元组动态数组上使用collect方法。示例:

use std::collections::HashMap;
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();

这里的类型标记HashMap<_,_>不能省略,因为collect可以作用于许多不同的数据类型,如果不指明类型的话,Rust就无法知道想要的具体类型。

(2)、哈希映射与所有权:

对于实现了Copy trait的类型,例如i32,它们的值会被简单地复制到哈希映射中,而对于String这种持有所有权的值,其值将会转移且所有权会被转移给哈希映射。示例:

use std::collections::HashMap;
let field_name = String::from("Favorite color");
let field_value = String::from("Blue");

let mut map = HashMap::new();
map.insert(filed_name,filed_value);
//filed_name和filed_value在这里开始失效

如果是将值的引用插入到哈希映射中,那么这些值是不会被移动到哈希映射中,这些引用所指向的值必须要保证,在哈希映射有效时,自己也是有效的。

(3)、访问哈希映射中的值:

可以通过将键传入到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 = scores.get(&team_name);

因为get返回的是一个Option<&V>,所以这里的结果也被装到了Some里,如果哈希映射中没有键所对应的值,那么就会返回None。
同样可以使用for循环来遍历哈希映射中所有的键值对。示例:

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

(4)、更新哈希映射:

i、覆盖旧值:

当将一个键值对插入到哈希映射后,接着使用同样的键并配以不同的值来继续插入,之前的键所关联的值就会被替换掉。示例:

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

这段程序会打印出{"Blus":25}

ii、只有键没有对应值时插入数据:

在实际工作中,常常需要检测一个键是否存在对应值,如果不存在,则为它插入一个值。哈希映射中提供了一个被称为entry的专用API来处理这种情形,它接收我们想要检测的键作为参数,并返回一个叫作Entry的枚举作为结果。这个枚举指明了键所对应的值是否存在。示例:

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

iii、基于旧值来更新值:

哈希映射的另外一个常见用法是查找某个键所对应的值,并基于这个值来进行更新。

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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值