Rust开发⼲货集(1)--迭代器与消费器

alt

本内容是对 Rust开发干货集[1] 的实践与扩展.


iter() 不转移所有权


先简单解释下什么叫"转移所有权":

在 Rust 中,"转移所有权"(Ownership Transfer)是一种核心概念,它涉及变量和数据的所有权从一个实体转移到另一个实体。这种机制帮助 Rust 在编译时期管理内存安全,避免悬挂指针和内存泄漏等问题。

例如:

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // 所有权从 s1 转移到 s2

    // println!("{}", s1); // 这行会引发编译错误,因为 s1 不再拥有数据的所有权. 报错: error[E0382]: borrow of moved value: `s1`
    println!("{}", s2); // 正确,s2 现在拥有数据的所有权
}

当 s1 被赋值给 s2 时,s1 的所有权被转移给了 s2。这意味着 s1 不再有效,因此接下来如果使用 s1 将导致编译错误。


iter() 在 Rust 中用于创建集合的迭代器,比如在数组或向量上。iter() 不会转移集合的所有权。相反,它创建一个迭代器,该迭代器借用集合的内容:

fn main() {
    let v = vec![123];

    for i in v.iter() {
        println!("{}", i);
    }

    // v 仍然有效,因为 iter() 没有取得所有权
    println!("Vector: {:?}", v);
}

上例中,v.iter() 创建了一个迭代器,但 v 的所有权没有改变。因此,在迭代之后,仍然可以使用 v

这说明iter() 不转移所有权(因为所有权转移意味着原始变量不再有效)


另外几种创建迭代器的方法: iter_mut()into_iter()


iter_mut()

iter_mut() 方法用于创建一个可变借用(mutable borrow)的迭代器。其允许在迭代过程中修改集合中的元素。

(所有权并没有发生转移)

如下:

fn main() {
    let mut v = vec![123];

    for i in v.iter_mut() {
        *i *= 2// 将每个元素乘以 2
        println!("{}", i);
    }

    println!("{:?}", v); // 输出: [2, 4, 6]
}

v.iter_mut() 创建了一个可变迭代器,允许修改向量 v 中的每个元素


into_iter()

into_iter() 方法用于创建一个取得所有权(ownership)的迭代器---这意味着迭代器会消耗(consume)集合,并拥有其元素的所有权。这通常用于在迭代时转移集合中元素的所有权。


如下:

fn main() {
    let v = vec![123];

    for i in v.into_iter() {
        println!("{}", i); // 打印每个元素
    }

    // println!("{:?}", v); // 这行会编译错误,因为 v 的所有权已经被移动; error[E0382]: borrow of moved value: `v`
}

v.into_iter() 创建了一个获取 v 所有权的迭代器。迭代后,v 不再有效,因为它的所有权已经被迭代器 into_iter() 消耗。

into_iter() 会转移所有权。它创建一个获取集合所有权的迭代器,允许在迭代时转移集合中元素的所有权。一旦使用了 into_iter(),原始集合将不再有效,因为它的所有权已经被迭代器取得。


iter_mut() 用于需要修改集合中元素的场景,但并不转移所有权; 而 into_iter() 用于需要转移元素所有权的场景。


iter()cloned()方法


iter() 方法用于创建一个不可变引用的迭代器,而 cloned() 是这类迭代器的一个方法。cloned() 的作用是将迭代器中的每个元素通过调用其 clone 方法来创建一个新的实例。这通常用于当拥有一个包含引用的迭代器,但需要迭代器中的值的拷贝时。


cloned() 的作用:

  • 创建元素的拷贝cloned() 方法适用于迭代器的元素实现了 Clone trait。它会为每个元素创建一个新的实例,这是通过调用每个元素的 clone 方法实现的。

  • 不转移所有权:由于 cloned() 仅仅是创建元素的副本,它不会改变原始数据的所有权。

如下例: 假设有一个Vec,其中包含一些数字的引用,现在想要迭代这些数字的拷贝而不是引用本身:

use std::any::type_name;

fn print_type_of<T>(_: &T) {
    println!("变量类型为:{}", type_name::<T>());
}

fn main() {
    let nums = vec![123];

    for num in nums.iter() {
        println!("{}", num); // 输出: 1, 2, 3
        print_type_of(&num);
    }

    println!("-----分界线------");
    let nums_cloned: Vec<i32> = nums.iter().cloned().collect();

    println!("{:?}", nums_cloned); // 输出: [1, 2, 3]

    for num in nums_cloned {
        println!("{}", num); // 输出: 1, 2, 3
        print_type_of(&num);
    }
}

输出为:

1
变量类型为:&i32
2
变量类型为:&i32
3
变量类型为:&i32
-----分界线------
[123]
1
变量类型为:i32
2
变量类型为:i32
3
变量类型为:i32

在上例中,使用 cloned() 方法,可以将这些引用转换为实际的数字拷贝,从而创建一个新的 Vec<i32>。这样,nums_cloned 就包含了 nums 中每个元素的拷贝,而不是引用。

cloned() 在 Rust 中用于从迭代器中创建元素的拷贝,特别是当有一个包含引用的迭代器 并希望获得实际值的拷贝时。它是处理引用集合时常用的便捷方法。


iter_mut() 有没有 cloned()方法?


iter_mut() 方法返回的迭代器是一个可变引用的迭代器。由于 cloned() 方法是用于拷贝迭代器中的值,它通常与不可变引用的迭代器(如由 iter() 返回的迭代器)一起使用。cloned() 方法适用于那些实现了 Clone trait 的元素,它会创建每个元素的拷贝。

对于 iter_mut() 返回的迭代器,由于它提供的是对元素的可变引用(&mut T),使用 cloned() 方法是不适当的,也不符合 Rust 的安全性原则。可变引用的目的是允许修改集合中的元素,而不是创建它们的拷贝。如果需要修改元素并且需要它们的拷贝,应该首先通过其他方式创建拷贝,然后对这些拷贝进行修改。

因此,在实际的 Rust 编程实践中,iter_mut() 迭代器上不会使用 cloned() 方法。如果需要元素的拷贝,应该使用 iter() 方法来创建一个不可变引用的迭代器,然后在该迭代器上使用 cloned()


map/fold(reduce)/filter的作用


更多可参考 初探函数式编程---以Map/Reduce/Filter为例[2]


map用于对迭代器中的 每个元素 应用某个函数/执行某项(会发生修改的)操作,并返回一个新的迭代器。

例如:

let numbers = [12345];

let doubles = numbers.iter().map(|x| x * 2);
// doubles将包含[2, 4, 6, 8, 10]
fn main() {
    let numbers = [12345];

    let doubles = numbers.iter().map(|x| x * 2);

    for x in doubles {
        println!("{}", x);
    }
}

输出:

2
4
6
8
10


fold用于将迭代器中的元素进行累积计算,其基本语法是:

iter.fold(init, |acc, x| {
   // acc是累积值,x是当前元素
   // 返回更新后的acc
})

fold需要两个参数:

  • init:初始累积值
  • 闭包:接收当前累积值acc和元素x,返回更新后的acc

例如:

fn main() {
    let numbers = [12345];

    let sum = numbers.iter().fold(0, |acc, x| acc + x);

    println!("Sum is: {}", sum); // Sum is: 15
}

这里fold的init值为0,闭包中每次将acc和x相加,返回更新后的acc,最终将数组所有元素求和。


另一个例子,连接字符串:

fn main() {
    let strings = ["a""b""c"];

    let concat = strings.iter().fold(String::new(), |acc, s| acc + s);

    println!("Concat is: {}", concat); // Concat is: abc
}

fold可以将迭代中的元素进行任意累积计算,常见用法包括求和、乘积、字符串连接等。fold()消费器可以实现reduce逻辑



filter用于过滤迭代器中的元素,只保留满足条件的元素。

例如:

let numbers = [12345];

let evens = numbers.iter().filter(|&x| x % 2 == 0);
// evens将只包含[2, 4]
fn main() {
    let numbers = [12345];

    let evens: Vec<_> = numbers.iter().filter(|&x| x % 2 == 0).collect();

    for x in evens {
        println!("{}", x);
    }
}

输出:

2
4

filter_map() 可以同时完成转换和过滤


filter_map() 方法结合了过滤(filter)和映射(map)功能。

这个方法接收一个闭包,该闭包作用于迭代器的每个元素,并返回 Option<T> 类型。filter_map() 然后自动过滤掉所有返回 None 的元素,并将所有 Some 包裹的值提取出来,形成一个新的迭代器。

  • 过滤和转换filter_map() 允许同时对迭代器的元素进行过滤和转换。如果闭包返回 Some(value),则 value 被包含在新迭代器中;如果闭包返回 None,则该元素被过滤掉。

  • 可选的转换:与 map() 相比,filter_map() 允许根据元素的值选择性地包含或排除元素,而不是简单地映射每个元素到另一个值。


举个例子, 假设有一个字符串类型的向量,想将其中的每个字符串转换为整数。但不是所有的字符串都可以转换为整数(例如,某些字符串可能包含非数字字符,如"1ab")。

在这种情况下,就可以使用 filter_map() 来尝试解析每个字符串,并仅保留那些成功解析为整数的元素:

fn main() {
    let strings = vec!["3""seven""8""10"];

    let numbers: Vec<i32> = strings
        .into_iter()
        .filter_map(|s| s.parse::<i32>().ok())
        .collect();

    println!("{:?}", numbers); // 输出: [3, 8, 10]
}

在上例中,filter_map() 尝试将每个字符串转换为 i32。如果 parse 方法成功(即返回 Ok(value)),filter_map()Some(value) 包装的 value 加入到新的迭代器中。如果 parse 失败(即返回 Err),filter_map() 则通过 ok() 方法将 Err 转换为 None,从而过滤掉这些元素。

通过这种方式,filter_map() 使得同时进行过滤和映射变得简单而高效。


另外一些消费器


上面介绍的 mapfoldfilter ,都属于消费器, 消费器在Rust中是指能够消费迭代器的类型

另外还有一些常用的消费器,包括:

  • collect():将迭代器收集到集合类型如Vec/String中

collect()消费器可以实现集合类型转化

fn main() {
    println!("{:?}"vec![123].iter().collect::<Vec<_>>()); // [1, 2, 3]
}
  • sum():计算迭代器元素的和。
fn main() {
    // sum()的返回类型依赖于迭代器的元素类型,这里元素是i32,所以需要明确指定sum_value的类型为i32
    let sum_value1: i32 = [123].iter().sum();
    println!("{:?}", sum_value1); // 6

    // 或者使用泛型参数指定
    let sum_value2 = [123].iter().sum::<i32>();
    println!("{:?}", sum_value2); // 6
}
  • min()/max():找到迭代器最小/最大元素。
fn main() {
    let max = [123].iter().max();
    println!("{:?}", max.unwrap()); // 3
}
  • count():统计迭代器元素数量。
fn main() {
    let count = [12306].iter().count();
    println!("{:?}", count); // 5
}
  • any()/all():是否存在(某个元素)/所有元素满足条件。

any()消费器可以查找是否存在满⾜条件的元素,迭代器是惰性的,any消费器可能不需要遍历Iterator

fn main() {
    let exists = [123].iter().any(|x| *x == 2);
    println!("{:?}", exists); // true, 即判断Vec中是否有值为2的元素
}
fn main() {
    let numbers = [246];

    let result = numbers.iter().all(|x| x % 2 == 0);

    println!("{:?}", result); // true, 即判断Vec中所有元素是否都能被2整除
}
  • find():找到第一个满足条件的元素并输出,否则返回None
fn main() {
    let numbers = [1234];

    // 查找numbers中第一个等于3的元素,找到后返回Some(3)
    let result = numbers.iter().find(|&x| *x == 3);
    println!("{:?}", result); // Some(3)

    // 如果没有找到满足条件的元素,则会返回None:
    let result = numbers.iter().find(|&x| *x == 5);
    println!("{:?}", result); // None
}

这些消费器都会完全消费迭代器,将其结果返回。它们常用于迭代器计算的最后阶段,将迭代结果转化为具体值。

参考资料

[1]

Rust开发干货集: https://github.com/rustaceanclub/rust-slides/tree/master/20191216-GB-Rust%E5%BC%80%E5%8F%91%E5%B9%B2%E8%B4%A7%E9%9B%86

[2]

初探函数式编程---以Map/Reduce/Filter为例: https://juejin.cn/post/7270823313809752076

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/587028.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

3D视觉-结构光测量-线结构光测量

概述 线结构光测量中&#xff0c;由激光器射出的激光光束透过柱面透镜扩束&#xff0c;再经过准直&#xff0c;产生一束片状光。这片光束像刀刃一样横切在待测物体表面&#xff0c;因此线结构光法又被成为光切法。线结构光测量常采用二维面阵 CCD 作为接受器件&#xff0c;因此…

QT上位机开发(乘法计算小软件)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面一篇文章&#xff0c;我们学习了怎么创建qt的第一个工程&#xff0c;怎么用designer给qt修改界面。虽然我们到目前为止&#xff0c;还没有编写…

C#使用switch语句更改窗体颜色

目录 一、示例 二、生成 用switch多路选择语句及窗体的BackColor属性更改窗体的BackColor属性。该属性用于获取或设置控件的背景颜色。 可以使用Color结构的静态属性获取Color对象&#xff0c;如Color.Red&#xff1b;也可以使用Color结构的静态方法Color.FromArgb()&#xf…

Linux权限的基本理解

一:&#x1f6a9;Linux中的用户 1.1&#x1f966;用户的分类 &#x1f31f;在Linux中用户可以被分为两种用户: 超级用户(root):可以在Linux系统中做各种事情而不被约束普通用户:只能做有限的事情被权限约束 在实际操作时超级用户的命令提示符为#,普通用户的命令提示符为$,可…

python观察图像的直流分量——冈萨雷斯数字图像处理

原理 在数字图像处理中&#xff0c;图像的直流分量&#xff08;DC分量&#xff09;是指图像中的平均亮度水平。这个概念源自于傅里叶变换&#xff0c;其中信号可以分解为多个频率成分。在这个上下文中&#xff0c;直流分量对应于频率为零的成分&#xff0c;即信号的平均值。 在…

CSS一个纯样式花里胡哨的动态渐变背景块

使用SASS或CSS纯样式花里胡哨的动态渐变背景块 鼠标放在小方块上会放大并挤压周围方块&#xff0c;背景颜色会动态改变。 效果如下 HTML结构 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"vie…

基于JWT的用户token验证

1. 基于session的用户验证 2. 基于token的用户身份验证 3. jwt jwt代码实现方式 1. 导包 <dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.18.2</version> </dependency> 2. 在登录…

Spring Data Redis对象缓存序列化问题

相信在项目中&#xff0c;你一定是经常使用 Redis &#xff0c;那么&#xff0c;你是怎么使用的呢&#xff1f;在使用时&#xff0c;有没有遇到同我一样&#xff0c;对象缓存序列化问题的呢&#xff1f;那么&#xff0c;你又是如何解决的呢&#xff1f; Redis 使用示例 添加依…

C++/CLI——1简介

C/CLI——1简介 如果你是.net程序员&#xff0c;不免会用到C/C写的库。对于简单的调用&#xff0c;可以直接使用DllImport来完成就可以&#xff0c;详情可参考C#调用C/C从零深入讲解。但是对于复杂的C类和对象&#xff0c;尤其是类似于OCC的大型C项目&#xff0c;DllImport可能…

初识大数据,一文掌握大数据必备知识文集(6)

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

三子棋(c语言)

前言&#xff1a; 三子棋是一种民间传统游戏&#xff0c;又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏规则是双方对战&#xff0c;双方依次在9宫格棋盘上摆放棋子&#xff0c;率先将自己的三个棋子走成一条线就视为胜利。但因棋盘太小&#xff0c;三子棋在很多时候会出现和…

Android Studio配置国内镜像源和HTTP代理/解决:Android Studio下载gradle速度慢的问题

&#xff08;方案一&#xff09;Android Studio配置国内镜像源和HTTP代理 一、配置国内镜像源/依赖库 1.1 打开项目的setting.gradle.kts文件 配置进去 pluginManagement {repositories {maven { urluri ("https://www.jitpack.io")}maven { urluri ("https:…

【Matlab】CNN卷积神经网络时序预测算法

资源下载&#xff1a; https://download.csdn.net/download/vvoennvv/88681558 一&#xff0c;概述 CNN&#xff08;Convolutional Neural Network&#xff0c;卷积神经网络&#xff09;是一种前馈神经网络&#xff0c;主要用于处理具有类似网格结构的数据&#xff0c;例如图像…

神经网络中的分位数回归和分位数损失

在使用机器学习构建预测模型时&#xff0c;我们不只是想知道“预测值(点预测)”&#xff0c;而是想知道“预测值落在某个范围内的可能性有多大(区间预测)”。例如当需要进行需求预测时&#xff0c;如果只储备最可能的需求预测量&#xff0c;那么缺货的概率非常的大。但是如果库…

【机器学习合集】深度生成模型 ->(个人学习记录笔记)

深度生成模型 深度生成模型基础 1. 监督学习与无监督学习 1.1 监督学习 定义 在真值标签Y的指导下&#xff0c;学习一个映射函数F&#xff0c;使得F(X)Y 判别模型 Discriminative Model&#xff0c;即判别式模型&#xff0c;又称为条件模型&#xff0c;或条件概率模型 生…

【Linux】chage命令使用

chage命令 chage用来更改linux用户密码到期信息&#xff0c;包括密码修改间隔最短、最长日期、密码失效时间等。 语法 chage [参数] 用户名 chage命令 -Linux手册页 选项及作用 执行令 &#xff1a; chage --help 执行命令结果 参数 -d, --lastday 最近日期 …

【Electron】webview 实现网页内嵌

实现效果&#xff1a; 当在输入框内输入某个网址后并点击button按钮 , 该网址内容就展示到下面 踩到的坑&#xff1a;之前通过web技术实现 iframe 标签内嵌会出现 同源策略&#xff0c;同时尝试过 vue.config.ts 内配置跨域项 那样确实 是实现啦 但不知道如何动态切换 tagert …

Cisco模拟器-交换机端口的隔离

设计要求将某台交换机的端口划分在不同的VLAN。以实现连接在相同VLAN端口上的计算机可以通信&#xff0c;而连接在不同VLAN端口上的计算机无法通信的目的。 通过设计&#xff0c;一方面可以加强计算机网络的安全&#xff0c;另一方面通过隔绝不同VLAN间的广播包也可以提高网络…

GcExcel:DsExcel 7.0 for Java Crack

GcExcel:DsExcel 7.0-高速 Java Excel 电子表格 API 库 Document Solutions for Excel&#xff08;DsExcel&#xff0c;以前称为 GcExcel&#xff09;Java 版允许您在 Java 应用程序中以编程方式创建、编辑、导入和导出 Excel 电子表格。几乎可以部署在任何地方。 创建、加载、…

numpy数组04-数组的轴和读取数据

一、数组的轴 在numpy中数组的轴可以理解为方向&#xff0c;使用0&#xff0c;1&#xff0c;2...数字表示。 对于一个一维数组&#xff0c;只有一个0轴&#xff0c;对于2维数组&#xff08;如shape&#xff08;2&#xff0c;2&#xff09;&#xff09;&#xff0c;有0轴和1轴…