news 2026/10/12 3:57:17

std1.97.1——iter模块总览

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
std1.97.1——iter模块总览

目录

  • 1. Iterator
  • 2. 三种迭代形式
  • 3. 实现Iterator
  • 4. for循环与IntoIterator
  • 5. 按引用迭代
  • 6. 适配器
  • 7. 惰性
  • 8. 无穷
  • 参考

1. Iterator

本模块的核心与灵魂是Iterator Trait。Iterator的核心部分如下所示:

traitIterator{typeItem;fnnext(&mutself)->Option<Self::Item>;}

迭代器有一个名为next的方法,调用它会返回一个Option<Item>。只要还有元素,调用next就会返回Some(Item);一旦元素全部耗尽,就会返回None,表示迭代已经结束。个别迭代器可以选择恢复迭代,因此之后再调用next,最终也可能在某个时刻重新开始返回Some(Item)。
Iterator的完整定义还包含许多其他方法,但它们都是构建在next之上的默认方法,因此可以免费获得它们。
迭代器还是可组合的,把它们串接起来以完成更复杂的处理是很常见的做法。

2. 三种迭代形式

有三种常见的方法可以从集合创建迭代器:
iter(),遍历产出&T
iter_mut(),遍历产出&mut T
into_iter(),遍历产出T
标准库中的各个类型会在合适的情况下实现这三者中的一个或多个。

3. 实现Iterator

创建你自己的迭代器包含两个步骤:先创建一个struct来保存迭代器的状态,然后为该struct实现Iterator。这正是本模块里有这么多struct的原因:每个迭代器和迭代器适配器各有一个。
示例:

// 首先,定义结构体/// 一个从一数到五的迭代器structCounter{count:usize,}// 我们希望计数从一开始,所以来加一个new()关联函数帮忙// 这并非严格必需,但很方便,注意我们把count初始化为零// 原因会在下文next()的实现中看到implCounter{fnnew()->Self{Self{count:0}}}// 然后,为我们的Counter实现IteratorimplIteratorforCounter{// 我们将用usize来计数typeItem=usize;// next()是唯一必须实现的方法fnnext(&mutself)->Option<Self::Item>{// 让计数加一,这就是我们从零开始的原因self.count+=1;// 检查计数是否已经结束ifself.count<6{Some(self.count)}else{None}}}fnmain(){// 现在可以使用了letmutcounter=Counter::new();println!("{:?}",counter.next());println!("{:?}",counter.next());println!("{:?}",counter.next());println!("{:?}",counter.next());println!("{:?}",counter.next());println!("{:?}",counter.next());}


像这样调用next未免有些重复。Rust有一种语法结构,可以在你的迭代器上不断调用next,直到它返回None。
另请注意,Iterator为nth、fold之类的方法提供了默认实现,这些实现在内部调用next。不过,如果某个迭代器不经过next就能更高效地计算出这类方法的结果,那么也可以为nth、fold之类的方法编写自定义实现。

4. for循环与IntoIterator

Rust的for循环语法实际上是对迭代器的语法糖。下面是一个基本的for示例:

fnmain(){letvalues=vec![1,2,3,4,5];forxinvalues{println!("{x}");}}

它会打印1到5这些数字,每个各占一行。不过你会在这里注意到一件事:我们从没在这个vector上调用任何东西来生成迭代器。这是怎么回事?
标准库中有一个用于把某物转换成迭代器的trait:IntoIterator。这个trait有一个方法into_iter,它把实现了IntoIterator的东西转换成一个迭代器。让我们再看一遍那个for循环,以及编译器把它转换成的东西:

fnmain(){letvalues=vec![1,2,3,4,5];forxinvalues{println!("{x}");}}

Rust把它脱糖为:

fnmain(){letvalues=vec![1,2,3,4,5];{letresult=matchIntoIterator::into_iter(values){mutiter=>loop{letnext;matchiter.next(){Some(val)=>next=val,None=>break,}letx=next;let()={println!("{x}");};}};result}}

首先,我们在值上调用into_iter()。然后,我们对返回的迭代器做match,一遍又一遍地调用next,直到见到None。到那时,我们break跳出循环,遍历就完成了。
这里还有一个微妙之处:标准库中包含一个有趣的IntoIterator实现:

impl<I:Iterator>IntoIteratorforI

换句话说,所有Iterator都实现了IntoIterator,方式就是直接返回自身。这带来两件事:
1. 如果你在编写一个Iterator,你可以直接把它用在for循环中。
2. 如果你在创建一种集合,为它实现IntoIterator就能让你的集合配合for循环使用。

5. 按引用迭代

由于into_iter()按值获取self,用for循环遍历一个集合会消耗掉该集合。很多时候,你会想在不消耗集合的前提下遍历它。许多集合提供了一些方法,用来产出遍历引用的迭代器,按惯例分别叫做iter()和iter_mut():

fnmain(){letmutvalues=vec![41];forxinvalues.iter_mut(){*x+=1;}forxinvalues.iter(){println!("{x}");}println!("{}",values.len());}

如果某个集合类型C提供了iter(),它通常也会为&C实现IntoIterator,其实现仅仅是调用iter()。类似地,提供了iter_mut()的集合C一般会通过委托给iter_mut()来为&mut C实现IntoIterator。这就带来了一种便捷的简写:

fnmain(){letmutvalues=vec![41];forxin&mutvalues{*x+=1;}forxin&values{println!("{x}");}println!("{}",values.len());}

虽然许多集合都提供iter(),但并非所有集合都提供iter_mut()。例如,修改HashSet<T>的键,可能会在键的哈希值发生变化后使集合进入不一致的状态,因此这种集合只提供iter()。

6. 适配器

接受一个Iterator并返回另一个Iterator的函数,通常被称为迭代器适配器,因为它们是适配器模式的一种形式。
常见的迭代器适配器包括map、take和filter。
如果某个迭代器适配器发生了panic,该迭代器将处于未指定但内存安全的状态。并且这个状态也不保证在不同版本的Rust之间保持一致,因此你应当避免依赖一个发生过panic的迭代器后续返回的确切值。

7. 惰性

迭代器以及迭代器适配器是惰性的。这意味着仅仅创建一个迭代器并不会做太多事情。在调用next之前,实际上什么都不会发生。当人们只为副作用而创建迭代器时,这一点有时会造成困惑。例如,map方法会在它遍历到的每个元素上调用一个闭包:

fnmain(){letv=vec![1,2,3,4,5];v.iter().map(|x|println!("{x}"));}

这不会打印任何值,因为我们只是创建了迭代器,而没有使用它。编译器会对这种行为发出警告:


为副作用而写map的惯用方式是使用for循环,或者调用for_each方法:

fnmain(){letv=vec![1,2,3,4,5];v.iter().for_each(|x|println!("{x}"));// 或者forxin&v{println!("{x}");}}

另一种常见的求值迭代器的方式,是使用collect方法产出一个新集合。

8. 无穷

迭代器不必是有限的。例如,一个开放式区间就是一个无穷迭代器:

fnmain(){letnumbers=0..;}

通常的做法是使用take迭代器适配器,把无限迭代器变成有限的:

fnmain(){letnumbers=0..;letfive_numbers=numbers.take(5);fornumberinfive_numbers{println!("{number}");}}

这会打印0到4这些数字,每个各占一行。
请记住:在无限迭代器上调用方法时——即使是那些数学上能在有限时间内确定结果的方法——也可能不会终止。具体来说,诸如min这类一般需要遍历迭代器中每一个元素的方法,对任何无限迭代器都不太可能成功返回。

fnmain(){letones=std::iter::repeat(1);letleast=ones.min().unwrap();// 糟糕!无限循环!// ones.min()会造成无限循环,因此执行不到这一行!println!("The smallest number one is {least}.");}

参考

1、Rust std::iter

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/12 3:57:02

技术讨论内容如何快速整理?

上周连着面了三批候选人&#xff0c;现场要同步记履历细节、项目经历和沟通要点&#xff0c;手上敲键盘的速度永远追不上对方说话的节奏&#xff0c;中途好几次漏了关键信息&#xff0c;回头翻录音又要拖很久进度。 后来试着用不同的工具处理这类场景&#xff0c;慢慢摸出了一些…

作者头像 李华
网站建设 2026/10/12 3:55:50

30 秒做出「真人感」AI Vlog:从本地生成人物到成片,保姆级教程

最近刷短视频&#xff0c;你应该也注意到了&#xff1a;一批「真人日常」感的 vlog 正在刷屏——手持镜头轻微晃动、窗边的自然光、素颜感的淡妆。唯一的区别是&#xff0c;画面里的人并不存在。 对普通创作者来说&#xff0c;这类内容的门槛正在快速下降。只需要一张人物图加…

作者头像 李华
网站建设 2026/10/12 3:55:50

官网Demo开发实战:从“8+1”需求拆解到组件复用与性能优化

大概一个月前&#xff0c;我接到一个需求&#xff1a;给某公司的官网做一套Demo&#xff0c;需求方原话就是“实现官网demo&#xff08;81&#xff09;”。当时我第一反应是这活儿挺简单&#xff0c;8个页面加1个页面而已&#xff0c;撑死一周搞定。结果做到一半就发现&#xf…

作者头像 李华
网站建设 2026/10/12 3:55:48

卫星通信元器件产线导入真空回流炉的选型复盘

去年秋天&#xff0c;老周给我打了个电话。他在成都一家做卫星通信T/R组件的厂里管封装产线&#xff0c;语气里透着那种"设备又卡住了"的疲惫。他们刚接了一个星上用的功放模块订单&#xff0c;基板是氮化铝&#xff0c;芯片是GaN功放管&#xff0c;要求焊接空洞率压…

作者头像 李华
网站建设 2026/10/12 3:55:40

Vue项目中前后端分离架构的设计与实现

一、前后端分离到底是什么&#xff1f;很多刚入门的朋友一听到“前后端分离”这个词&#xff0c;觉得很高大上。其实说白了&#xff0c;就是把一个网站或者一个系统拆成两拨人去干活&#xff1a;一拨人只管用户能看到的东西&#xff0c;比如网页的布局、按钮、图片、动画&#…

作者头像 李华
网站建设 2026/10/12 3:54:55

打卡信奥刷题(3622)用C++实现信奥题 P11827 [TOIP2024] 大步小步向前走

P11827 [TOIP2024] 大步小步向前走 题目背景 本题的 Special Judge 由 CuteMurasame 重构&#xff0c;以符合 -stdc14 标准。 题目描述 五条圣是电门中学的学生&#xff0c;他的梦想是成为职业足球选手。虽然他因为想每天练习足球而不想去上学&#xff0c;但为了不违反国民…

作者头像 李华