Rust入门(九):闭包与迭代器

Rust 的一个特点是函数式编程,可以将函数作为参数值或其他函数的返回值、将函数赋值给变量以供之后执行等等。闭包和迭代器是其中两个显著特点

闭包

闭包(closures)是可以保存在一个变量中或作为参数传递给其他函数的匿名函数,可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算,闭包允许捕获被定义时所在作用域中的值。

可以使用 || 包裹闭包中的参数,如果有多于一个参数,可以使用逗号分隔,比如 |param1, param2|,这个 let 语句意味着 expensive_closure 包含一个匿名函数的定义,

//定义闭包
let expensive_closure = |num| {
        num
};
//使用闭包
expensive_closure(intensity)

闭包不要求像 fn 函数那样在参数和返回值上注明类型,因为函数是暴露给用户的显式接口的一部分,而闭包储存在变量中并被使用,不用命名他们或暴露给库的用户调用。

闭包定义会为每个参数和返回值推断一个具体类型,如下面的例子,如果尝试调用闭包两次,第一次使用 String 类型作为参数而第二次使用 u32,则会得到一个错误:

let example_closure = |x| x;
//将闭包推断为String
let s = example_closure(String::from("hello"));
//运行失败
let n = example_closure(5);

如果代码需要多次运行闭包,可以创建一个存放闭包和调用闭包结果的结构体。该结构体只会在需要结果时执行闭包,并会缓存结果值,如下:字段 valueOption<u32> 类型的。在执行闭包之前,value 将是 None。如果使用 Cacher 的代码请求闭包的结果,这时会执行闭包并将结果储存在 value 字段的 Some 成员中。接着如果代码再次请求闭包的结果,这时不再执行闭包,而是会返回存放在 Some 成员中的结果。

struct Cacher<T>
where
    T: Fn(u32) -> u32,
{
    calculation: T,
    value: Option<u32>,
}

impl<T> Cacher<T>
where
    T: Fn(u32) -> u32,
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation,
            value: None,
        }
    }

    fn value(&mut self, arg: u32) -> u32 {
        match self.value {
            Some(v) => v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                v
            }
        }
    }
}

我们保存一个新的 Cacher 实例来存放闭包。接着,在每一个需要结果的地方,调用 Cacher 实例的 value 方法。然而这样的设计。在第一次调用之后,就不能在更新,因此可以尝试修改 Cacher 存放一个哈希 map 而不是单独一个值,哈希 map 的 key 将是传递进来的 arg 值,而 value 则是对应 key 调用闭包的结果值。

闭包与环境捕获

闭包可以作为内联匿名函数来使用,但闭包还有另一个函数所没有的功能:他们可以捕获其环境并访问其被定义的作用域的变量。如下:即便 x 并不是 equal_to_x 的一个参数,equal_to_x 闭包也被允许使用变量 x,因为它与 equal_to_x 定义于相同的作用域。

fn main() {
    let x = 4;

    let equal_to_x = |z| z == x;

    let y = 4;

    assert!(equal_to_x(y));
}

闭包可以通过三种方式捕获其环境,他们直接对应函数的三种获取参数的方式:获取所有权,可变借用和不可变借用:

这三种捕获值的方式被编码为如下三个 Fn trait:

  • FnOnce 消费从周围作用域捕获的变量,闭包周围的作用域被称为其 环境environment。为了消费捕获到的变量,闭包必须获取其所有权并在定义闭包时将其移动进闭包。其名称的 Once 部分代表了闭包不能多次获取相同变量的所有权的事实,所以它只能被调用一次。
  • FnMut 获取可变的借用值所以可以改变其环境
  • Fn 从其环境获取不可变的借用值

当创建一个闭包时,Rust 根据其如何使用环境中变量来推断我们希望如何引用环境。由于所有闭包都可以被调用至少一次,所以所有闭包都实现了 FnOnce 。那些并没有移动被捕获变量的所有权到闭包内的闭包也实现了 FnMut ,而不需要对被捕获的变量进行可变访问的闭包则也实现了 Fn

例如:equal_to_x 闭包不可变的借用了 x(所以 equal_to_x 具有 Fn trait),因为闭包体只需要读取 x 的值;

如果你希望强制闭包获取其使用的环境值的所有权,可以在参数列表前使用 move 关键字,如下:x 被移动进了闭包,因为闭包使用 move 关键字定义。接着闭包获取了 x 的所有权,同时 main 就不再允许在 println! 语句中使用 x 了。去掉 println! 即可修复问题

fn main() {
    let x = vec![1, 2, 3];

    let equal_to_x = move |z| z == x;
	//这是不允许的
    println!("can't use x here: {:?}", x);

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

    assert!(equal_to_x(y));
}

迭代器

迭代器(iterator)负责遍历序列中的每一项和决定序列何时结束的逻辑。当使用迭代器时,我们无需重新实现这些逻辑。

在 Rust 中,迭代器是惰性的(lazy),这意味着在调用方法使用迭代器之前它都不会有效果,一旦创建迭代器之后,可以选择用多种方式利用它,比如使用循环进行遍历

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

let v1_iter = v1.iter();

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

迭代器都实现了一个叫做 Iterator 的定义于标准库的 trait,其中,Item 类型将是迭代器返回元素的类型,nextIterator 实现者被要求定义的唯一方法。next 一次返回迭代器中的一个项,封装在 Some 中,当迭代器结束时,它返回 None

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // 此处省略了方法的默认实现
}

直接在迭代器上调用 next 方法可以消耗迭代器里一个元素,如下的例子:定义一个可变的 v1_iter ,它是一个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);
    }

注意 : 使用 for 循环时无需使 v1_iter 可变因为 for 循环会获取 v1_iter 的所有权并在后台使 v1_iter 可变。

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

迭代器的api

Iterator trait 有一系列不同的由标准库提供默认实现的方法

  • 迭代器的创建

迭代器的构造有多种方式

use std::slice::Iter;
use std::iter::Iterator;
let it = 0..10;//[0,9]的整数
let it = 0..=10;//[0,10]的整数
let it = 0..;// 产生从0开始的无限整数流(迭代器)
let it = [1,2,3,4].iter();
let it = vec![5; 3].iter();

// chain()将两个迭代器顺序拼接合并
let it = (0..10).chain(20..30);
  • 迭代器的遍历

可以使用whilefor 等多种方式遍历迭代器

//while-next模式
while let Some(x) = iter.next() {
    print!("{:?} ", x);
}
// for-in循环对迭代器进行遍历
for i in it {
    print!("{} ", i);
}
// for_each()对元素逐个处理,内部调用的是fold(),
[1,2,3,4].iter().for_each(|x| {
    print!("{x} ");//1 2 3 
});
// 元素个数跟个数较少的迭代器相同
let a = [1,2,3].iter();
let b = ['a', 'b', 'c', 'd'].iter();
let x = a.zip(b);
//(1, 'a')(2, 'b')(3, 'c')
  • 迭代器获取元素

除了 next 逐个获取元素之外,还可以通过多种方式更加灵活的获得迭代器的下一个元素:

take(k) 取前面k个元素,只可调用一次

assert_eq!(vec![1,2,3], (1..10).take(3).collect::<Vec<_>>());

nth(k) 取得迭代器剩余元素中第k个位置的元素,位置从0开始;之后,迭代器跳转到下一个位置

let mut it = [1, 2, 3].iter();
assert_eq!(Some(&1), it.nth(0));
assert_eq!(Some(&2), it.nth(0));
assert_eq!(Some(3), (0..4).nth(3));

last()只取最后一个元素,只能调用一次。

assert_eq!((1..4).last(), Some(3));
  • 迭代器的变换

rev() 对迭代器进行反转

vec![0, 1, 2, 3, 4].iter().rev().for_each(|x|print!("{x},"));
//输出:9,8,7,6,5,4,3,2,1,0,

skip(k) 使得跳过k个元素

assert_eq!(vec![2,3], (0..4).skip(2).collect::<Vec<_>>());

step_by(k),从第一个元素开始,每k个取一个出来

//0 2 4 6 8 10 
(0..=10).step_by(2).for_each(|x| print!("{x} "));
assert_eq!(vec![0,2,4,6], (0..7).step_by(2).collect::<Vec<_>>());

zip()将2个迭代器合并为一对一元组迭代器

let a = [1,2,3].iter();
let b = ['a', 'b', 'c', 'd'].iter();
let x = a.zip(b);
//(1, 'a')(2, 'b')(3, 'c')

map 方法使用闭包来调用每个元素以生成新的迭代器,如下的例子,用闭包创建了一个新的迭代器,对其中 vector 中的每个元素都被加 1

let v1: Vec<i32> = vec![1, 2, 3];
v1.iter().map(|x| x + 1);

迭代器的 filter 方法获取一个使用迭代器的每一个项并返回布尔值的闭包,如果闭包返回 true,其值将会包含在 filter 提供的新迭代器中。如果闭包返回 false,其值不会包含在结果迭代器中。

let s = "1 a 2 b 3 c";
let a = s.split_ascii_whitespace().filter_map(|x|x.parse::<i32>().ok()).collect::<Vec<i32>>();
//[1, 2, 3]
  • 求值结算

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

assert_eq!([1,2,3].iter().sum::<i32>(), 6);

max 将会返回一系列值的最大值,而 min 将返回最小值

assert_eq!([1,2,3].iter().max(), Some(&3));
assert_eq!([1,2,3].iter().min(), Some(&1));

count 可以输出迭代器内部元素的个数

assert_eq!([1,2,3].iter().count(), 3);

all 判断迭代器中是否所有元素都符合闭包predicate指定的测试。

let b = (2..10).into_iter().all(|i|i>0);
println!("{}", b);//true

fold()方法,通过传入一个初始值和一个函数操作,对迭代器中的每一个元素依次进行处理,最后返回结果。

assert_eq!(3, (1..3).fold(0, |acc, x|acc+x));//1+2
assert_eq!(6, (1..3).fold(0, |acc, x|acc+2*x));//2*1 + 2*2
  • collect

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]);

创建自定义迭代器

正如之前提到的,迭代器的定义中唯一要求提供的方法就是 next 方法。一旦定义了它,就可以使用所有其他由 Iterator trait 提供的拥有默认实现的方法来创建自定义迭代器了,如下是自定义的一个迭代器,它首先返回 1。如果 count 值小于 6,next 会返回封装在 Some 中的当前值,不过如果 count 大于或等于 6,迭代器会返回 None

struct Counter {
    count: u32,
}
impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}
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
        }
    }
}
#[test]
fn calling_next_directly() {
    let mut counter = Counter::new();
    assert_eq!(counter.next(), Some(1));
    assert_eq!(counter.next(), Some(2));
}

通过定义 next 方法实现 Iterator trait,我们现在就可以使用任何标准库定义的拥有默认实现的 Iterator trait 方法了,因为他们都使用了 next 方法的功能。

#[test]
fn using_other_iterator_trait_methods() {
    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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

摸鱼老萌新

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值