Rust 和 C++ 中的迭代器在概念上有很大不同。
C++
在 C++ 中,迭代器类似于指针。迭代器引用一个对象,可以通过增加操作引用到下一个对象,并且可以与其他迭代器进行比较以检查相等性。迭代器也可以不引用任何对象,它们可以引用序列中的“结束”元素,或者它们可以是“奇异”的(类似于空指针)。一些迭代器支持额外的操作,例如向前和向后移动、随机访问和复制。
C++ 中的指针是有效的迭代器,但也有其他类型的迭代器。
迭代器并不表示元素序列,至少在约定上不是这样。在 C++ 中,如果你需要一个元素序列,你需要一对迭代器*:一个用于开头,一个用于结尾。你不必按顺序迭代元素,可以做各种其他事情。例如,在 C++ 中反转数组,可以使用迭代器实现:
#include <algorithm>
#include <iterator>
#include <cstdio>
#include <utility>
template <typename T, std::size_t N>
void reverse_array(T (&arr)[N]) {
using std::swap;
auto left = std::begin(arr), right = std::end(arr);
while (left < right) {
--right;
swap(*left, *right);
++left;
}
}
int main() {
int x[] = {1, 2, 3, 4, 5};
reverse_array(x);
for (const auto it : x) {
std::printf("%d\n", it);
}
return 0;
}
但是您可以快速将其推广到使用双向迭代器的任何容器:
#include <algorithm>
#include <iterator>
#include <list>
#include <cstdio>
#include <utility>
template <typename Iterator>
void reverse_any(Iterator left, Iterator right) {
using std::swap;
while (left != right) {
--right;
if (left == right)
break;
swap(*left, *right);
++left;
}
}
int main() {
std::list<int> list{1, 2, 3, 4, 5};
reverse_any(std::begin(list), std::end(list));
for (const auto it : list) {
std::printf("%d\n", it);
}
return 0;
}
Rust
在Rust中,迭代器类似于一个切片。迭代器指向一系列对象,并且可以使用next()
方法从迭代器访问元素。从某种意义上说,这意味着Rust中的迭代器同时具有begin
和end
迭代器†。在Rust中重新实现上面的C++代码,则会得到如下结果:
fn reverse_any<'a, T: 'a, Iter>(mut iter: Iter)
where
Iter: DoubleEndedIterator<Item = &'a mut T>,
{
while let Some(left) = iter.next() {
if let Some(right) = iter.next_back() {
std::mem::swap(left, right);
}
}
}
fn main() {
let mut v = [1, 2, 3, 4, 5];
reverse_any(v.iter_mut());
println!("{:?}", v);
}
这种方法的附加好处是安全性。迭代器失效是C++程序中最常见的错误源之一,但Rust完全消除了这个问题。
代价是,如果您想要改变元素,则在Rust中只能使用单个(可能是双端)迭代器,而在C++中,您可以有许多迭代器与同一个容器一起工作。虽然单向和双向范围是迭代器的最常见情况,但有些算法使用了C++提供的额外灵活性。
我能想到的一个简单例子是C++的std::remove_if
。一个简单的remove_if
实现需要使用三个迭代器:两个用于跟踪正在扫描的元素范围的迭代器,以及一个用于跟踪正在编写的元素的第三个迭代器。您可以将std::remove_if
转换为Rust,但它将无法使用普通的Rust迭代器并仍然原地修改容器。
另一个简单的例子是荷兰国旗问题,通常使用三个迭代器。解决这个问题的方法经常用于快速排序的元素分区,因此这是一个重要的问题。
摘要
Rust迭代器几乎等价于C++起点+终点迭代器对。C++允许您使用多个迭代器并将迭代器向前和向后移动。Rust保证您不会意外使用无效的迭代器,但您一次只能使用一个迭代器,并且它只能向一个方向移动。
我不知道有何术语来区分这些类型的迭代器。请注意,Rust风格的迭代器更为常见,C#,Python,Java等中的迭代器工作方式相同,但可能具有略微不同的名称(在C#中称为“枚举器”)。
脚注
*:技术上这不是真的。您只需要一个迭代器,但是传统上要使用一对迭代器,并且库函数通常操作一对迭代器(因此,如果要使用这些函数,则“需要”两个迭代器)。拥有(start,end)对并不意味着序列有界,结束迭代器可以是无限远。想象一下在数学上有一个区间(0,∞)……∞不是真正的数字,它只是一个占位符,让您知道该范围在右侧是无界的。
†:记住,仅因为C++中存在“end”迭代器并不意味着序列实际上有一个结束。 C++中的某些结束迭代器就像无穷大一样。 它们不指向有效的元素,无论您向前迭代多少次,都不会到达无穷大。 在Rust中,等效构造是一个永远不返回None
的迭代器。