Rust 学习笔记:迭代器

发布于:2025-05-29 ⋅ 阅读:(25) ⋅ 点赞:(0)

Rust 学习笔记:迭代器

在 Rust 中,迭代器负责遍历每个项的逻辑。迭代器是懒惰的,这意味着它们在调用消耗迭代器的方法之前没有作用。

示例:

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

    let v1_iter = v1.iter();

    for val in v1_iter {
        println!("Got: {val}");
    }

在这个示例中,我们将迭代器的创建与在 for 循环中使用迭代器分开。当使用 v1_iter 中的迭代器调用 for 循环时,迭代器中的每个元素都会在循环的一次迭代中使用,并打印出每个值。

Iterator trait 和 next 方法

所有迭代器都实现一个名为 Iterator 的 trait,该 trait 在标准库中定义。这个特性的定义是这样的:

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;

    // methods with default implementations elided
}

注意,这个定义使用了一些新的语法:type Item 和 Self::Item,它们定义了与这个 trait 相关联的类型。这段代码表明,实现 Iterator trait 还需要定义一个 Item 类型,并且该 Item 类型用于下一个方法的返回类型。换句话说,Item 类型将是迭代器返回的类型。

Iterator trait 只需要实现者定义一个方法:next 方法,每次返回迭代器的一个项,包装在 Some 中,迭代结束时返回 None。

我们可以直接在迭代器上调用 next 方法。下面的代码演示了在 vector 创建的迭代器上重复调用 next 会返回哪些值。

    #[test]
    fn iterator_demonstration() {
        let v1 = vec![1, 2, 3];

        let mut v1_iter = v1.iter();

        assert_eq!(v1_iter.next(), Some(&1));
        assert_eq!(v1_iter.next(), Some(&2));
        assert_eq!(v1_iter.next(), Some(&3));
        assert_eq!(v1_iter.next(), None);
    }

注意,我们需要将 v1_iter 设置为可变的:在迭代器上调用 next 方法会改变迭代器用于跟踪其在序列中的位置的内部状态,每次调用都会消耗迭代器中的一个项。

在使用 for 循环时,不需要将 v1_iter 设为可变的,因为循环在后台获取了 v1_iter 的所有权并使其可变。

还要注意,从调用 next 得到的值是对 vector 中值的不可变引用。iter 方法在不可变引用上生成一个迭代器。如果要创建一个接受 v1 的所有权并返回拥有的值的迭代器,可以调用 into_iter 方法。类似地,如果要在可变引用上迭代,可以调用 iter_mut 方法。

使用迭代器的方法

Iterator trait 有许多不同的方法,由标准库提供默认实现。其中一些方法在其定义中调用 next 方法,这就是为什么在实现 Iterator trait 时需要实现 next 方法的原因。

调用 next 的方法称为消费适配器,因为调用它们会耗尽迭代器。

一个例子是 sum 方法,它获得迭代器的所有权,并通过重复调用 next 来遍历项,从而消耗迭代器。当它遍历时,它将每个项目添加到运行总数中,并在迭代完成时返回总数。

    #[test]
    fn iterator_sum() {
        let v1 = vec![1, 2, 3];

        let v1_iter = v1.iter();

        let total: i32 = v1_iter.sum();

        assert_eq!(total, 6);
    }

注意,在调用 sum 之后不允许使用 v1_iter,因为 sum 将获得调用它的迭代器的所有权。

生成其他迭代器的方法

迭代器适配器是在 Iterator trait 上定义的不消耗迭代器的方法。相反,它们通过改变原始迭代器的某些方面来生成不同的迭代器。

下面代码是调用迭代器适配器方法 map 的示例,该方法接受一个闭包,在遍历每个项目时调用该闭包。map 方法返回一个新的迭代器,用于生成修改后的项。

    let v1: Vec<i32> = vec![1, 2, 3];

    v1.iter().map(|x| x + 1);

运行代码会得到一个警告:我们指定的闭包永远不会被调用。这个警告提醒了我们原因:迭代器适配器是惰性的,我们需要在这里消费迭代器。

在这里插入图片描述

为了修复这个警告并消费迭代器,我们将使用 collect 方法,此方法使用迭代器并将结果值收集到集合数据类型中。

    let v1: Vec<i32> = vec![1, 2, 3];

    let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();

    assert_eq!(v2, vec![2, 3, 4]);

因为 map 接受闭包,所以可以指定想对每个项执行的任何操作。

还可以将多个调用链接到迭代器适配器,以可读的方式执行复杂的操作。但是,由于所有迭代器都是惰性的,因此必须调用一个使用适配器的方法来从调用迭代器适配器中获取结果。

使用闭包捕获它们的环境

许多迭代器适配器将闭包作为参数,通常我们指定作为迭代器适配器参数的闭包将是捕获其环境的闭包。

示例:

#[derive(PartialEq, Debug)]
struct Shoe {
    size: u32,
    style: String,
}

fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
    shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn filters_by_size() {
        let shoes = vec![
            Shoe {
                size: 10,
                style: String::from("sneaker"),
            },
            Shoe {
                size: 13,
                style: String::from("sandal"),
            },
            Shoe {
                size: 10,
                style: String::from("boot"),
            },
        ];

        let in_my_size = shoes_in_size(shoes, 10);

        assert_eq!(
            in_my_size,
            vec![
                Shoe {
                    size: 10,
                    style: String::from("sneaker")
                },
                Shoe {
                    size: 10,
                    style: String::from("boot")
                },
            ]
        );
    }
}

对于本例,我们将使用接受闭包的 filter 方法。闭包从迭代器获取一个元素并返回 bool 值。如果闭包返回 true,则该值将包含在 filter 生成的迭代器中。如果闭包返回 false,则不包含该值。

在 shoes_in_size 的函数体中,调用 into_iter 来创建一个接受 vector 对象所有权的迭代器。然后调用 filter 将该迭代器改编为一个新的迭代器。filter 方法的闭包从环境中捕获 shoe_size 参数,并将其值与每只鞋的尺寸进行比较,只保留指定尺寸的鞋子。最后,调用 collect 将经过调整的迭代器返回的值收集到函数返回的向量中。


网站公告

今日签到

点亮在社区的每一天
去签到