通常情况下,你的程序必须处理多个数据实例,因此,可使用集合类型。根据你的需要以及数据驻留在内存中的位置,Rust 提供了多种内置类型来储存数据集合。首先我们有数组和元组。然后我们的标准库中有动态集合类型,将介绍其中最常用的类型,它们基本上是对某些其他变量所有的连续数据的视图。
数组
数组具有固定长度,可以储存相同类型的元素。它们用 [T, N]
表示,其中 T
表示任意类型,N
表示数组元素数量。数组的大小不能用变量表示,并且必须是 usize 的字面值:
fn main() {
let numbers: [u8;10] = [1,2,3,4,5,6,7,8,9,10];
let floats = [0.1f64,0.2,0.3];
println!("Number: {}",numbers[5]);
println!("Float: {}",floats[2]);
}
上述代码中,我们声明了一个整形数组,其中包含`10`个元素,并在左侧指定了元素的类型。在第二个浮点型数组中,我们将类型指定为数组中第一个元素的后缀,即 `0.1f64`.这是指定类型的另一种方法。
元组
元组与数组的不同之处在于,数组的元素必须具有相同的类型,而元组中的元素可以具有不同的类型。元组是异构集合,可用与将不同类型的元素储存在一起,从函数返回多个值时可以使用它。
fn main() {
let num_and_str: (u8, &str) = (40, "Hava a good day!");
println!("{:?}",num_and_str);
let (num, string) = num_and_str;
println!("From tuple: Number: {},String: {}",num,string);
}
在上述代码中,num_and_str
是一个包含两个元素的元组,即(u8, &str)
。我们还将已经 声明的元组中的值提取到单个变量中。输出元组后,我们将已经声明的元组解构为 num
和 string
变量,并自动推断它们的类型。该代码非常简洁。
向量
向量和数组类似,不过它们的内容和长度不需要事先指定,并且可以按需增长。
它们是在堆上分分配的。我们既可以使用构造函数 Vec::new
,也可以使用宏vec![]
创建它们:
fn main() {
let mut numbers_vec: Vec<u8> = Vec::new();
numbers_vec.push(1);
numbers_vec.push(2);
let mut vec_with_macro = vec![1];
vec_with_macro.push(2);
let _ = vec_with_macro.pop();//忽略空格
let message = if numbers_vec == vec_with_macro {
"They are equal"
}else {
"Nah! They look different to me"
};
println!("{} {:?} {:?}",message,numbers_vec,vec_with_macro);
}
在上述代码中,我们以不同方式创建了两个向量,即 numbers_vec
和 vec_with_macro
。 我们可以使用 push()
方法将元素推送到 vector
中,并可以使用 pop()
方法删除元素。如果你 希望了解更多相关的方法,可以参考官方帮助文档,还可以使用 for
循环语句迭代访问 vector
,因为它们也实现了 Iterator
特征。
键/值对
Rust 还为我们提供了 键/值对,它可以用于存储键/值对。它们来自std::collections
模块,名为 HashMap。它们是使用构造函数HashMap::new
创建的:
use std::collections::HashMap;
fn main() {
let mut fruits = HashMap::new();
fruits.insert("apple",3);
fruits.insert("mango",6);
fruits.insert("orange",2);
fruits.insert("avocado",7);
for (k , v) in &fruits {
println!("I got {} {}", v, k);
}
fruits.remove("orange");
let old_avocado = fruits["avocado"];
fruits.insert("avocado",old_avocado + 5);
println!("\nI now have {0} avocados{0}",fruits["avocado"]);
}
上述代码中,我们新建了一个名为 fruits
的 HashMap
。然后使用insert
方法向其中插入了一些水果元素以及相关的计数。接下来,我们使用for
循环遍历 键/值对,其中通过&fruits
引用我们的水果映射结构,因为我们只希望读取其中的键和值。默认情况下,for
循环将使用该值。
在上述情况下,for
循环返回一个包含两个字段的元组(k, v)。还有单独的方法keys()
和value()
分别用于迭代访问键和值。用于哈希化HashMap
类型键的哈希算法基于Robin hood
开放寻址方案,但我们可以根据用例和性能替换成自定义哈希方案。
切片
切片是获取集合类型视图的常用做法。大多数用例是对集合类型中特定区间的元素进行只读访问。切片基本上是指针或引用,指向现有集合类型中某个其他变量所拥有的连续区间。实际上,切片是指向堆栈或堆中某处现有数据的胖指针,这意味着它还包含关于指向元素多少的信息,以及指向数据的指针。
切片用&[T]
表示,其中 T 表示任意类型。它们的使用方式与数组非常类似:
fn main() {
let mut numbers: [u8; 4] = [1,2,3,4];
{
let all: &[u8] = &numbers[..];
println!("All of them: {:?}",all);
}
{
let first_two: &mut [u8] = &mut numbers[0..2];
first_two[0] = 100;
first_two[1] = 99;
}
println!("Look ma! Ican modify through slices: {:?}",numbers);
}
上述代码中有一个 numbers
数组,这是一个堆栈分配值。然后我们使用&numbers[..]
语法对数组中的数字进行切片并储存到变量all
中,其类型为&[u8]
。末尾的[..]表示我们要获取整个集合。这里我们需要用到&
,是因为切片是不定长类型(unsized type),不能将切片储存为裸值—即仅在指针后面。
**&[u8]**
表示**all**
是一个指向**u8**
类型元素的不可变切片。**<font style="color:rgb(13, 13, 13);">&mut numbers[0..2]</font>**<font style="color:rgb(13, 13, 13);"> </font>
表示创建一个从索引**<font style="color:rgb(13, 13, 13);">0</font>**
到**<font style="color:rgb(13, 13, 13);">2</font>**
(不包括**<font style="color:rgb(13, 13, 13);">2</font>**
)的切片,它是可变的,所以我们可以修改这些元素。**&mut [u8]**<font style="color:rgb(13, 13, 13);"> </font>
表示**first_two**
是一个指向**u8**
类型元素的可变切片。
我们还可以提供范围([0..2])
以获得任意区间的切片。切片也可以,可变地获得。first_two
是一个可变切片,我们可以通过它修改原是的number
数组。
:::info 注意
&str
类型也属于切片类型([u8])
,与其他字节切片的 唯一区别在于,它们保证为 UTF-8
。也可以在 Vec
或 String
上执行切片。
:::