通常情况下,你的程序必须处理多个数据实例,因此,可使用集合类型。根据你的需要以及数据驻留在内存中的位置,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 上执行切片。
:::
