Rust第十二节-闭包与迭代器

12. 闭包与迭代器

12.1闭包

12.1.1 闭包的构成

闭包可以作为参数、返回值或者将其存储在一个变量中。

我们先来举个例子来讲讲闭包的构成:

rust 复制代码
let y: i32 = 1;
let get_sum = |x| x + y;
let result = get_sum(1);
println!("{}", result)

这里我们声明了一个闭包get_sum,它和函数的声明完全一样,只是我这是用||来作为闭包传参的入口,并且省略了{},当然我们可以加上:

rust 复制代码
let get_sum = |x| { let sum = x + y; sum };

从上面的例子中我们会发现,闭包可以去读取环境中的变量,这是它和函数的区别

12.1.2 闭包的类型

从上面的例子来看,其实我们并没有在闭包内部给它定义类型,其实Rust内部给我们推导了类型,例如:

rust 复制代码
let get_self = |x| x;
get_self(1);
get_self('1'); // error

当我们第一次传入了一个i32的数字类型,这个时候其实已经被默认推导成了i32的类型,当我们再传入一个不同类型的值,会编译出错。

在我们声明一个闭包时,它会自动被推导为三种特征,怎么决定使用哪个特征,是看闭包内怎么去使用读区环境量的参数:

  • 所有的闭包都自动实现了 FnOnce 特征,因此任何一个闭包都至少可以被调用一次
  • 不对捕获变量改变 的闭包自动实现了 Fn 特征
  • 改变了捕获变量的值但是没有改变捕获变量所有权 的闭包自动实现了 FnMut 特征

我们先来举一个Fn的列子: 我们可以看到我们并没有对y值进行修改,所以这里会被推导为Fn类型;

rust 复制代码
let y = 1;
let get_self = |x| x + y;

我们再来看一个FnMut的例子: 这里我们在函数里面使用了引用来去修改环境参数的字符串,但是没有修改所有权,所以会被推导为FnMut

rust 复制代码
let mut str = String::from("hello");
let mut combine_str = || {
    &str.push_str("world");
};
combine_str();
println!("{:?}", str);

我们最后来看一个强制改变了捕获环境值所有权的用例,它会自动推导为FnOnce,这里我们使用了move关键字,他可以将捕获的环境参数所有权强制移动到闭包内部,例如:

rust 复制代码
let str = String::from("hello");
let combine_str = move || str;
combine_str();

刚刚我们举的例子都是闭包作为一个变量进行存储,我们接下里写一个将闭包作为参数传递的例子:

rust 复制代码
let s = String::from("hello");

let update_str = || println!("{}", s);

fn executeFn<T>(f: T)
where
    T: Fn() -> (),
{
    f();
}

executeFn(update_str);

fn executeFnOnce<T>(f: T)
where
    T: FnOnce() -> (),
{
    f();
}
executeFnOnce(update_str);

fn executeFnMut<T>(mut f: T)
where
    T: FnMut() -> (),
{
    f();
}
executeFnMut(update_str)

12.2 迭代器

12.1.1 创建迭代器

首先我们先来创建一个迭代器,如下:

rust 复制代码
let arr =  vec![1,2,3];
let iter_arr = arr.iter();

我们这里使用iter方法,实际是对arr数组的不可变引用 ,当我们先获取可变引用时,我们需要使用iter_mut方法,或者当我们想直接获取其所有权时,我们可以使用into_iter方法。

12.1.2 Iterator 和 next

所以可迭代的类型都是实现了特征Iterator,并且实现next方法,让我们来看看iterator特征:

rust 复制代码
trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
}

其中Item是一个关联类型,后续会详细讲到。这里我们实现的特征,需要实现next,执行next后,会消耗迭代器。

12.1.3 消耗迭代器

接下里我们来讲讲怎么去消耗迭代器,我们首先创建一个迭代器,如:

rust 复制代码
let iter_arr = vec![1, 2, 3].iter();

然后我们来执行sum方法,它会去执行next方法,迭代每个元素,并且将其加在一起,并且返回得到一个总和,如:

rust 复制代码
let sum: i32 = iter_arr.sum();
println!("{}", sum);

接下来我们来看看怎么用其他的方法来创建迭代器。

12.1.4 使用其他方法创建迭代器

我们来使用map方法来将数组中每个值+1,如下:

rust 复制代码
let arr = vec![1, 2, 3];
let iter_arr = arr.iter().map(|x| x + 1);
println!("{:?}", iter_arr)

我们来看看打印值,如:

rust 复制代码
// Map { iter: Iter([1, 2, 3]) }

我们可以看到,我们的值并没有加1。这是因为我们的迭代器并且执行消耗,所以值不会变化,这里我们执行collect方法去消耗这个迭代器之后,会将其转化为一个动态数组,如:

rust 复制代码
let arr = vec![1, 2, 3];
let iter_arr: Vec<i32> = arr.iter().map(|x| x + 1).collect();
println!("{:?}", iter_arr) // [2,3,4]

12.1.5 使用环境参数

我们还可以去使用闭包的特性去使环境参数。比如,我们使用可迭代类型的filter方法,它会根据根据我们传入的值进行筛选,如果为true则返回,false不返回。例如:

rust 复制代码
#[derive(Debug)]
struct User {
    name: String,
    age: i32,
}
let arr = vec![
    User {
        name: String::from("stephen"),
        age: 18,
    },
    User {
        name: String::from("kyrie"),
        age: 34,
    },
    User {
        name: String::from("kris"),
        age: 22,
    },
];
let less_age: i32 = 30;
// 筛选出年纪小于less_age的用户
let iter_arr: Vec<&User> = arr.iter().filter(|user| user.age < less_age).collect();
println!("{:#?}", iter_arr);

这里我们写了一个简单的用例,根据环境参数less_age去使用filter方法去筛选,年纪小于less_age的用户。

12.1.6 自定义迭代器

最后,我们来自定义一个迭代器,我们首先写一个结构体Counter,如:

rust 复制代码
struct Counter {
    count: i32,
}

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

impl Iterator for Counter {
    type Item = i32;
    fn next(&mut self) -> Option<Self::Item> {
        self.count = self.count + 1;
        if self.count < 6 {
            Some(self.count)
        } else {
            None
        }
    }
}

let mut counter = Counter::new();
println!("{:?}", counter.next()); // 1
println!("{:?}", counter.next()); // 2
println!("{:?}", counter.next()); // 3
println!("{:?}", counter.next()); // 4
println!("{:?}", counter.next()); // 5
println!("{:?}", counter.next()); // None

我们定义了一个结构体,并且为它声明了一个new方法,并且实现Iterator特征,然后我们去执行next方法,就可以可以拿到1-5的值。

12.3 使用迭代器、闭包改进I/O项目

接下来,我们来使用学习的新内容来修改之前做的一个检索功能。

12.3.1 修改入参

之前我们在声明Confignew方法时,我们使用到了一个叫clone克隆的方法,这是为了拿到传入参数的所有权方便后续操作,但是这个方法是很消耗性能的,所以我们这里我们使用迭代器去改造一下。我们先看看之前的这个方法:

rust 复制代码
impl Config {
fn new(args: &[String]) -> Result<Config, &str> {
    if args.len() < 3 {
        return Err("输入的参数不能小于2位");
    }
    let match_text = args[1].clone(); // 消耗性能
    let file_name = args[2].clone();
    Ok(Config {
        match_text,
        file_name,
    })
}
}

我们首先将我们的入参修改,我们之前传入的是一个动态字符串,现在我们直接传入一个迭代器:

rust 复制代码
Config::new(env::args())

并且我们需要在new方法中修改:

rust 复制代码
fn new(mut args: env::Args)

因为迭代器是要消耗每个元素的,所以我们需要加上mut。 因为env::arg()获取到的第一个参数是项目,所以我们需要执行一下arg.next()。然后第二次执行next方法时,我们可以拿到我们需要匹配的值,第三次则可以拿到文件名。因为next拿到的是Option枚举,所以我们需要使用match去拿到最终的值,如:

rust 复制代码
impl Config {
    fn new(mut args: env::Args) -> Result<Config, &'static str> {
        // if args.len() < 3 {
        //     return Err("输入的参数不能小于2位");
        // }
        // let match_text = args[1].clone();
        // let file_name = args[2].clone();
        args.next();
        let match_text = match args.next() {
            Some(text) => text,
            None => return Err("未获取到匹配内容"),
        };
        let file_name = match args.next() {
            Some(name) => name,
            None => return Err("未获取到文件名"),
        };
        Ok(Config {
            match_text,
            file_name,
        })
    }
}

12.3.2 修改匹配

还有一处,之前我们进行匹配的操作时,我们使用的for循环,现在我们直接使用filter方法,去过滤,直接省去for循环遍历这一步,例如:

rust 复制代码
fs::read_to_string().unwrap().lines().filter(|line| line.contains(match_text)).collect();

我们直接使用lines方法获取文件的迭代器,并且执行filter去完成过滤匹配,为什么在rust中,我们使用的更多的是迭代器还不是for循环呢?

  1. 迭代器是一种更高层次的抽象概念,避免我们在开发时疲于去声明、维护各个变量的状态以及处理循环中的逻辑
  2. 迭代器编译出来的是一种零开销底层代码,性能要优于for循环遍历

所以在Rust中,我们经常看到迭代器的使用,并且我们也要习惯于这种写法。

相关推荐
百锦再2 分钟前
第10章 错误处理
java·git·ai·rust·go·错误·pathon
baozj3 分钟前
🚀 手动改 500 个文件?不存在的!我用 AST 撸了个 Vue 国际化神器
前端·javascript·vue.js
用户40993225021211 分钟前
为什么Vue 3的计算属性能解决模板臃肿、性能优化和双向同步三大痛点?
前端·ai编程·trae
海云前端112 分钟前
Vue首屏加速秘籍 组件按需加载真能省一半时间
前端
蛋仔聊测试13 分钟前
Playwright 中route 方法模拟测试数据(Mocking)详解
前端·python·测试
零号机24 分钟前
使用TRAE 30分钟极速开发一款划词中英互译浏览器插件
前端·人工智能
疯狂踩坑人1 小时前
结合400行mini-react代码,图文解说React原理
前端·react.js·面试
Mintopia1 小时前
🚀 共绩算力:3分钟拥有自己的文生图AI服务-容器化部署 StableDiffusion1.5-WebUI 应用
前端·人工智能·aigc
街尾杂货店&1 小时前
CSS - transition 过渡属性及使用方法(示例代码)
前端·css
CH_X_M1 小时前
为什么在AI对话中选择用sse而不是web socket?
前端