前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >rust迭代器

rust迭代器

作者头像
zy010101
发布2023-05-26 10:41:26
3980
发布2023-05-26 10:41:26
举报
文章被收录于专栏:程序员程序员

迭代器(Iterator)

迭代器模式允许你对一个序列的项进行某些处理。迭代器(iterator)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。

在 Rust 中,迭代器是 惰性的(lazy),这意味着在调用方法使用迭代器之前它都不会有效果

For循环和迭代器

在之前关于流程控制的文章中,介绍For循环的时候,介绍过for循环形式的原理。for循环时间上就是在使用迭代器。不过我们通常使用的形式是简写。

使用方法

等价使用方式

所有权

for item in collection

for item in IntoIterator::into_iter(collection)

转移所有权

for item in &collection

for item in collection.iter()

不可变借用

for item in &mut collection

for item in collection.iter_mut()

可变借用

for循环能够对迭代器进行循环迭代。(正如上面表格中的等价形式一样,for是对迭代器进行的。)

next方法

迭代器之所以成为迭代器,是因为实现了Iterator trait。要实现该特征,最主要的就是实现其中的 next 方法,该方法控制如何从集合中取值,最终返回值的类型是关联类型 Item。

代码语言:javascript
复制
pub trait Iterator {
    type Item;

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

    // 省略其余有默认实现的方法
}

for 循环通过不停调用迭代器上的 next 方法,来获取迭代器中的元素。我们手动调用next来获取元素。

代码语言:javascript
复制
fn main() {
    let num = [1, 2, 3];
    let mut iter = num.into_iter();

    println!("{}", iter.next().unwrap());
    println!("{}", iter.next().unwrap());
    println!("{}", iter.next().unwrap());
    println!("{}", iter.next().unwrap());       // 迭代器用尽的时候返回值None,因此unwrap会直接panic
}

我们通过into_iter()方法获取了一个迭代器,然后进行迭代。

  1. next 方法返回的是 Option 类型,当有值时返回 Some(i32),无值时返回 None
  2. 遍历是按照迭代器中元素的排列顺序依次进行的,因此我们严格按照数组中元素的顺序取出了 Some(1),Some(2),Some(3)
  3. 手动迭代必须将迭代器声明为 mut 可变,因为调用 next 会改变迭代器其中的状态数据(当前遍历的位置等),而 for 循环去迭代则无需标注 mut,因为它会帮我们自动完成

next 方法对迭代器的遍历是消耗性的,每次消耗它一个元素,最终迭代器中将没有任何元素,只能返回 None。

IntoIterator 特征

由于 Vec 动态数组实现了 IntoIterator 特征,因此可以通过 into_iter 将其转换为迭代器,那如果本身就是一个迭代器,该怎么办?实际上,迭代器自身也实现了 IntoIterator,标准库早就帮我们考虑好了:

代码语言:javascript
复制
impl<I: Iterator> IntoIterator for I {
    type Item = I::Item;
    type IntoIter = I;

    #[inline]
    fn into_iter(self) -> I {
        self
    }
}

IntoIterator中实现了into_iter方法,并且该方法返回IntoIterator对象本身。迭代器本身也是可迭代的。类似于python的迭代器不仅实现了__next__方法,还实现了__iter__方法,而__iter__方法就返回了对象本身。因此,我们可以写出下面这样的代码。

代码语言:javascript
复制
for n in num.into_iter().into_iter().into_iter() {
    println!("{}", n);
}

这是OK的,因为迭代器本身的into_iter方法返回的就是迭代器本身。同时上面的例子也告诉我们,迭代器的遍历是消耗性的,你传入一个耗尽的迭代器,返回的也是耗尽的迭代器。但是这种链式调用的方式有时候很实用。 在rust里into_ 之类的,都是拿走所有权,_mut 之类的都是可变借用,剩下的就是不可变借用。(大概率,传统习惯上是这样)

IntoIterator和Iterator

rust和python类似,区分了可迭代对象和迭代器对象。Iterator 就是迭代器特征,只有实现了它才能称为迭代器,才能调用 next。 而 IntoIterator 强调的是某一个类型如果实现了该特征,它可以通过 into_iter,iter 等方法变成一个迭代器。称为可迭代对象

消费者适配器

只要迭代器上的某个方法 A 在其内部调用了 next 方法,那么 A 就被称为消费性适配器:因为 next 方法会消耗掉迭代器上的元素,所以方法 A 的调用也会消耗掉迭代器上的元素。其中一个例子是 sum 方法,它会拿走迭代器的所有权,然后通过不断调用 next 方法对里面的元素进行求和:

代码语言:javascript
复制
fn main() {
    let v1 = vec![1, 2, 3];

    let v1_iter = v1.iter();

    let total: i32 = v1_iter.sum();

    assert_eq!(total, 6);

    // v1_iter 是借用了 v1,因此 v1 可以照常使用
    println!("{:?}",v1);

    // 以下代码会报错,因为 `sum` 拿到了迭代器 `v1_iter` 的所有权
    // println!("{:?}",v1_iter);
}

如代码注释中所说明的:在使用 sum 方法后,我们将无法再使用 v1_iter,因为 sum 拿走了该迭代器的所有权。sum的源码如下所示:

代码语言:javascript
复制
fn sum<S>(self) -> S
where
    Self: Sized,
    S: Sum<Self::Item>,
{
    Sum::sum(self)
}

迭代器适配器

既然消费者适配器是消费掉迭代器,然后返回一个值。那么迭代器适配器,顾名思义,会返回一个新的迭代器,这是实现链式方法调用的关键。与消费者适配器不同,迭代器适配器是惰性的,意味着你需要一个消费者适配器来收尾,最终将迭代器转换成一个具体的值:

代码语言:javascript
复制
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]);

collect

上面代码中,使用了 collect 方法,该方法就是一个消费者适配器,使用它可以将一个迭代器中的元素收集到指定类型中,这里我们为 v2 标注了 Vec<> 类型,就是为了告诉 collect:请把迭代器中的元素消费掉,然后把值收集成 Vec<> 类型,至于为何使用 _,因为编译器会帮我们自动推导。

map

map 会对迭代器中的每一个值进行一系列操作,然后把该值转换成另外一个新值,该操作是通过闭包 |x| x + 1 来完成:最终迭代器中的每个值都增加了 1,从 [1, 2, 3] 变为 [2, 3, 4]。

zip

zip 把两个迭代器合并成一个迭代器,新迭代器中,每个元素都是一个元组,由之前两个迭代器的元素组成。例如将形如 [1, 2, 3, 4, 5] 和 [2, 3, 4, 5] 的迭代器合并后,新的迭代器形如 [(1, 2),(2, 3),(3, 4),(4, 5)]

filter

filter 对迭代器中的元素进行过滤,例如将形如 [1, 2, 3, 4, 5]的数组经过filter传递的闭包|x| x % 2 == 0处理,则保留元素[2, 4]

实现 Iterator 特征

创建一个计数器:

代码语言:javascript
复制
struct Counter {
    count: u32,
}

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

我们为计数器 Counter 实现了一个关联函数 new,用于创建新的计数器实例。下面我们继续为计数器实现 Iterator 特征:

代码语言:javascript
复制
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
        }
    }
}

最后,使用迭代器。

代码语言:javascript
复制
fn main() {
    let count = Counter::new();

    for c in count {
        println!("{}", c);
    }
}

可以看出,实现自己的迭代器非常简单,但是 Iterator 特征中,不仅仅是只有 next 一个方法,那为什么我们只需要实现它呢?因为其它方法都具有默认实现,所以无需像 next 这样手动去实现,而且这些默认实现的方法其实都是基于 next 方法实现的。

下面的代码演示了部分方法的使用:

代码语言:javascript
复制
let sum: u32 = Counter::new()
    .zip(Counter::new().skip(1))
    .map(|(a, b)| a * b)
    .filter(|x| x % 3 == 0)
    .sum();
assert_eq!(18, sum);

参考资料

rust程序设计语言

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-05-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 迭代器(Iterator)
    • For循环和迭代器
      • next方法
        • IntoIterator 特征
          • IntoIterator和Iterator
            • 消费者适配器
              • 迭代器适配器
                • collect
                • map
                • zip
                • filter
              • 实现 Iterator 特征
                • 参考资料
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档