Rust 学习笔记(24)-迭代器

参考章节《Rust 程序设计语言》第13.2章 使用迭代器处理元素序列

迭代器是遍历数据的一种方式

迭代器模式允许你对一个序列的项进行某些处理。
迭代器(iterator)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。
在 Rust 中,迭代器是 惰性的(lazy),这意味着在调用方法使用迭代器之前它都不会有效果。

先看一个例子

  1. 迭代器的第一种使用方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    let v1 = vec![1, 2, 3];

    let v1_iter = v1.iter(); // 创建一个迭代器

    // 通过 for 循环的方式来使用迭代器
    for val in v1_iter {
        println!("Got: {}", val);
    }
}
  1. 迭代器的第二种使用方法

Iterator trait 和 next 方法

迭代器都实现了一个叫做 Iterator 的定义于标准库的 trait。这个 trait 的定义看起来像这样

1
2
3
4
5
6
7
8
pub trait Iterator {
    type Item; // 这段代码表明实现 Iterator trait 要求同时定义一个 Item 类型,这个 Item 类型被用作 next 方法的返回值类型。换句话说,Item 类型将是迭代器返回元素的类型。

    // next 是 Iterator 实现者被要求定义的唯一方法。next 一次返回迭代器中的一个项,封装在 Some 中,当迭代器结束时,它返回 None。
    fn next(&mut self) -> Option<Self::Item>;

    // 此处省略了方法的默认实现
}

可以直接调用迭代器的 next 方法,获取迭代器中的下一个元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#[test]
fn iterator_demonstration() {
    let v1 = vec![1, 2, 3];

    let mut v1_iter = 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 方法改变了迭代器中用来记录序列位置的状态。
换句话说,代码消费了,或使用了迭代器。每一次 next 调用都会从迭代器中消费一个项。
你可能注意到上面使用 for 循环方式时没有使 v1_iter 可变,那是因为 for 循环会获取 v1_iter 的所有权并在后台使 v1_iter 可变。

另外需要注意到从 next 调用中得到的值是 vector 的不可变引用。iter 方法生成一个不可变引用的迭代器。
如果我们希望迭代可变引用,则可以调用 iter_mut 而不是 iter
如果我们需要一个获取 v1 所有权并返回拥有所有权的迭代器,则可以调用 into_iter 而不是 iter

消费适配器

Iterator trait 有一系列不同的由标准库提供默认实现的方法
一些方法在其内部调用了 next 方法,这些调用 next 方法的方法被称为 消费适配器(consuming adaptors),因为调用他们会消耗迭代器。

sum 方法获取迭代器的所有权并反复调用 next 来遍历迭代器,因而会消费迭代器。当其遍历每一个项时,它将每一个项加总到一个总和并在迭代完成时返回总和。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#[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);
}

迭代器适配器

迭代器适配器的特点是会产生一个新的迭代器

Iterator trait 中定义了另一类方法,被称为 迭代器适配器(iterator adaptors),他们允许我们将当前迭代器变为不同类型的迭代器。
可以链式调用多个迭代器适配器。不过因为所有的迭代器都是惰性的,必须调用一个消费适配器方法以便获取迭代器适配器调用的结果

map 方法调用迭代器的 next 方法,并使用闭包来处理每个元素(这里为每个元素 + 1),最后生成新的迭代器。

1
2
3
4
5
6
7
8
9
fn main() {
    let v1: Vec<i32> = vec![1, 2, 3];

    // v1.iter().map(|x| x + 1); 这段代码不能编译,因为你必须调用一个消费适配器来消费这个迭代器

    let v2: Vec<_> = v1.iter().map(|x| x + 1).collect(); // collect 方法是一个消费适配器,这个方法消费迭代器并将结果收集到一个数据结构中。

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

书中使用闭包获取环境这一节讲了一个例子,我觉得太啰嗦了,所以没有写,有兴趣可以去看看,也不难,无非是讲 filter 迭代器适配器的用法

自定义迭代器

迭代器定义中唯一要求提供的方法就是 next 方法。一旦定义了它,就可以使用所有其他由 Iterator trait 提供的拥有默认实现的方法来创建自定义迭代器了!

让我们创建一个只会从 1 数到 5 的迭代器。首先,创建一个结构体来存放一些值,接着实现 Iterator trait 将这个结构体放入迭代器中并在此实现中使用其值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

// 实现 Iterator trait 
impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

总结一下

  1. 迭代器可以通过 for 循环和 next 方式来使用它
  2. 消费适配器方法会获取迭代器的所有权
  3. 迭代器适配器方法会产生一个新的迭代器
  4. 自定义迭代器需要实现 Iterator traitnext 方法
updatedupdated2025-03-012025-03-01