当前位置:嗨网首页>书籍在线阅读

17-集合

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

1.3.12 集合

通常情况下,你的程序必须处理多个数据实例,因此,可使用集合类型。根据你的需要以及数据驻留在内存中的位置,Rust提供了多种内置类型来存储数据集合。首先,我们有数组和元组。然后,我们的标准库中有动态集合类型,将介绍其中最常用的类型,即项目列表(vector)和键/值对(map)。最后,我们还引用了被称为切片的集合类型,它们基本上是对某些其他变量所拥有的连续数据的视图。让我们先从数组开始介绍。

数组

数组具有固定长度,可以存储相同类型的元素。它们用[T,N]表示,其中T表示任意类型,N表示数组元素的数量。数组的大小不能用变量表示,并且必须是usize的字面值:

// arrays.rs
fn main() {
    let numbers: [u8; 10] = [1, 2, 3, 4, 5, 7, 8, 9, 10, 11];
    let floats = [0.1f64, 0.2, 0.3];
    println!("Number: {}", numbers[5]);
    println!("Float: {}", floats[2]);
}

在上述代码中,我们声明了一个整型数组,其中包含10个元素,并在左侧指定了元素的类型。在第二个浮点型数组中,我们将类型指定为数组中第一个元素的后缀,即0.1f64。这是指定类型的另一种方法。接下来,让我们来介绍元组。

元组

元组与数组的不同之处在于,数组的元素必须具有相同的类型,而元组中的元素可以具有不同的类型。元组是异构集合,可用于将不同类型的元素存储在一起,从函数返回多个值时可以使用它。考虑下列应用元组的代码:

// tuples.rs
fn main() {
    let num_and_str: (u8, &str) = (40, "Have 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![]创建它们:

// vec.rs
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创建的:

// hashmaps.rs
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 {} avocados", fruits["avocado"]);
}

在上述代码中,我们新建了一个名为fruits的HashMap。然后使用insert方法向其中插入了一些水果元素以及相关的计数。接下来,我们使用for循环遍历键/值对,其中通过&fruits引用我们的水果映射结构,因为我们只希望读取其中的键和值。默认情况下,for循环将使用该值。在上述情况下,for循环返回一个包含两个字段的元组((k,v))。还有单独的方法keys()和values()分别用于迭代访问键和值。用于哈希化HashMap类型键的哈希算法基于Robin hood开放寻址方案,但我们可以根据用例和性能替换成自定义哈希方案。接下来,让我们看看切片。

切片

切片是获取集合类型视图的常用做法。大多数用例是对集合类型中特定区间的元素进行只读访问。切片基本上是指针或引用,指向现有集合类型中某个其他变量所拥有的连续区间。实际上,切片是指向堆栈或堆中某处现有数据的胖指针,这意味着它还包含关于指向元素多少的信息,以及指向数据的指针。

切片用&[T]表示,其中T表示任意类型。它们的使用方式与数组非常类似:

// slices.rs
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! I can modify through slices: {:?}", numbers);
}

在上述代码中有一个numbers数组,这是一个堆栈分配值。然后我们使用&numbers[..]语法对数组中的数字进行切片并存储到变量all中,其类型为&[u8]。末尾的[..]表示我们要获取整个集合。这里我们需要用到&,是因为切片是不定长类型(unsized types),不能将切片存储为裸值——即仅在指针后面。与之有关的细节将会在第7章详细介绍。我们还可以提供范围([0..2])以获得任意区间的切片。切片也可以可变地获得。first_two是一个可变切片,我们可以通过它修改原始的numbers数组。

对细心的读者来说,你会发现在上述代码中,我们在进行切片时额外使用了一对花括号。它们用于隔离从不可变引用中获取切片的可变引用的代码。没有它们,代码将无法进行编译。第5章将会对它们进行详细介绍。

22.png 注意

&str类型也属于切片类型([u8]),与其他字节切片的唯一区别在于,它们保证为UTF-8。也可以在Vec或String上执行切片。

接下来,让我们来讨论迭代器。