5分钟速读之Rust权威指南(二十二)迭代器

迭代器

类似JS中数组的forEach,map等迭代方法,在rust中,也有迭代器的概念,区别是rust的for_each,map方法被称之为迭代适配器,迭代适配器是惰性的(lazy)。除非你主动调用,否则它们不会产生任何的实际效果。

创建迭代器

使用iter方法创建一个迭代器,并调用next进行消费:

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

// 创建迭代器
// 由于迭代器内部有维护指针位置,每次迭代会更新指针,所以需要标记mut
let mut iter = v1.iter();

// 消费迭代器
println!("{:?}", iter.next());
println!("{:?}", iter.next());
println!("{:?}", iter.next());
println!("{:?}", iter.next());

// Some(1)
// Some(2)
// Some(3)
// None
复制代码

其实和JS中的很像,可以回忆一下:

var arr= [1,2,3];

// 创建迭代器
var iter = arr[Symbol.iterator]();

// 消费迭代器
iter.next();
iter.next();
iter.next();
iter.next();

// {value: 1, done: false}
// {value: 2, done: false}
// {value: 3, done: false}
// {value: undefined, done: true}
复制代码

消费迭代器

除了使用next方法手动调用迭代器以外,还可以使用for循环来消费,过程中迭代的结果如果是Some类型,for循环会继续迭代,直到遇到None才会停止:

let arr = vec![1, 2, 3];
let iter = arr.iter();

// 因为for循环会消费迭代器,所以会取得iter的所有权
for item in iter {
  println!("{}", item)
  // 1
  // 2
  // 3
}

println!("{:?}", iter) // 报错,所有权已经被移动
复制代码

迭代器trait

并不是所有类型都可以进行迭代,能够迭代的类型都需要实现Iterator trait:

pub trait Iterator {
  // 会在后边介绍:关联类型
  type Item;
  // 每一个迭代器都拥有next方法,
  // 它会在每次被调用时返回一个包裹在Some中的迭代器元素,
  // 并在迭代结束时返回None。
  fn next(&mut self) -> Option<Self::Item>;
}
复制代码

为自定义结构体实现迭代器trait:

// 定义一个计数器结构体
struct Counter {
  // 迭代最大值
  max: u32,
  // 当前值
  current: u32
}

// 实现迭代器trait
impl Iterator for Counter {
  // 指定每次迭代的类型
  type Item = u32;

  // 实现next方法
  fn next(&mut self) -> Option<Self::Item>{
    // 当前值大于等于最大值时,也就是迭代结束的时候,返回None
    if self.current >= self.max {
      None
    } else {
      // 获取当前值
      let val = self.current;
      // 为下次迭代累加1
      self.current += 1;
      // 返回当前值
      Some(val)
    }
  }
}

let counter = Counter {
  current: 0,
  max: 3
};

// 可以直接使用for循环
for item in counter {
  println!("{}", item);
  // 1
  // 2
  // 3
}
复制代码

迭代器类型

在消费iter迭代器时,我们取到的是每一个成员的不可变引用,事实上一共有三种迭代器可用:

  • iter:获得成员的不可变引用
  • iter_mut:获得成员的可变引用
  • into_iter:获得成员的所有权

消费适配器

rust内置了许多消费迭代器的方法,这些方法会获取迭代器的所有权并反复调用next来遍历元素,例如sum方法对数组中的成员求和:

let arr = [1, 2, 3];
let iter = v3.iter();

// sum方法会取得迭代器所有权(可以看一下sum的签名),
// 所以迭代器无法再继续使用
let total: i32 = iter.sum();
println!("{}", total); // 6
复制代码

迭代器适配器

rust还内置了许多用于通过迭代器链式操作成员的方法,这些方法都是惰性的,只有在最后调用一个消耗适配器的方法后,迭代适配器方法才会执行,例如map迭代器适配器:

let arr = [1, 2, 3];
// map好创建一个新的迭代器并返回,
// 由于迭代器适配器是惰性的,除非消费迭代器,否则什么事情都不会发生,
// 因为没有进行消费,此时编译会报提醒
arr.iter().map(|x| x + 1);
复制代码

使用collect消费适配器:

// collect会消耗迭代器并将结果值收集到某种集合数据类型中
let arr = [1, 2, 3];

// 注意使用collect需要指定最终类型
let arr2: Vec<i32> = arr.iter().map(|x| x + 1).collect();
// 等价于
let arr2 = arr.iter().map(|x| x + 1).collect::<Vec<i32>>();

println!("{:?}", arr2); // [2, 3, 4]
复制代码

filter迭代适配器用于过滤成员:

#[derive(Debug)]
struct Shoe {
  size: u32
}
let arr = vec![
  Shoe { size: 40 },
  Shoe { size: 41 },
  Shoe { size: 42 },
];

// filter将过滤size大于40的Shoe
let arr2: Vec<_> = arr.iter().filter(|x| x.size > 40).collect();

println!("{:?}", arr2);
// [Shoe { size: 41 }, Shoe { size: 42 }]
复制代码

一个更复杂的迭代适配器例子:

// 使用上面自定义结构体Counter
let sum: u32 = Counter {
  current: 0,
  max: 5
}
// zip:组合成: (1,2) (2,3) (3,4) (4,5),由于最后一个(5, None)由于其中一个是None,将不会被生成
.zip(Counter {
  current: 0,
  max: 5
  // skip:跳过第一个值,也就是与 1,2,3,4进行zip操作
}.skip(1))
// map映射后:2 6 12 20
.map(|(a, b)| a * b)
// filter过滤后:6 12
.filter(|x| x % 3 == 0)
// sum求和:6 + 12
.sum();

println!("{}", sum); // 18
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享