C ++迭代器的典型示例是指针,可用于指向C数组中的元素,如下所示:
int array[] = {1, 2, 3, 4};
int* begin = std::begin(array); //Starting iterator
int* end = std::end(array) //Ending iterator
for(int* i = begin; i < end; i++)
{
std::cout << *i << ',';
}
//Prints 1, 2, 3, 4
这很简单。迭代器的定义来自 cplusplus.com 是
迭代器是指向一系列元素(例如数组或容器)中的某个元素的任何对象,它能够使用一组运算符迭代该范围的元素...
这是有道理的;在上面的代码中有两个迭代器( begin
和 end
迭代器),它用了一个 for
循环并递增。
在Rust中,迭代器的使用方式如下:
let vect = vec![1, 2, 3, 4];
let vect_iter = vect.iter();
什么?要迭代它,你可以:
vect_iter.next();
vect_iter.next();
我在Rust文档中找不到任何指针的确切定义,但是看看 Iterator
特征,似乎迭代器是容器的包装器,通过以某种方式标准化逻辑(如果这样做有意义),可以更容易地处理。
我的主要问题是:
- 有哪些主要区别?
- 为什么Rust以这种方式拥有迭代器,为什么它们表达方式如此不同?
- 在C ++中是否有Rust类型的迭代器?
- 是否有C ++ - 在Rust中键入迭代器?
- 它们被称为具体的东西吗? (内部外部?)
迭代器是编程语言中的一个概念,用于指代允许的构造 迭代 过度集合或元素序列。这个概念是故意模糊的,这是一个概念!它没有规定任何具体的实施。
为了更容易区分C ++和Rust,我将使用不同的名称:
- 将命名C ++迭代器 游标,
- Rust迭代器将被命名 流。
是的,那些完全是武断的。请注意,如果您查看Java或C#等语言,您会发现它们也使用流。
C ++
首先,不要使用cplusplus.com。 cppreference.com要好得多。
迭代器是指向一系列元素(例如数组或容器)中的某个元素的任何对象,它能够使用一组运算符迭代该范围的元素...
简单,而且 错误。
游标可以是:
通常,奇异值用于表示:
- 迭代序列的结尾:
vec.end()
,
- 没有元素:
std::find(...)
。
您可以增加光标,有时也可以减少光标。如果你这样做,你通常需要一个 对 游标知道何时停止。
为什么C ++使用这样的表示?因为C就是这样做的,它运作得很好......虽然它很容易出错。
锈
Rust努力安全,并且喜欢易于使用的API。这排除了一对游标:
- 一对游标不安全:你可以轻松地迭代越界,你可以获得别名引用,
- 一对游标很容易出错:很容易意外地将游标与两个不同的序列配对。
为了控制 界限, 别名 并避免 对不匹配,你必须使用一个对象;因此像流一样的API。
该 Iterator
Rust中的API让人联想到Java和C#,尽管Rust通过使用它来改进它 Option<T>
所以,而不是笨拙 hasNext()
/next()
呼叫对,它提供了一种方法 next()
它们都推进了流,并可能发出信号。
结论
Rust和C ++都有一种迭代元素集合的方法:
- C ++提供类似C的方式,灵活但容易出错,
- Rust提供现代化的方式,安全但不太灵活。
这两种语言也提供 外部 和 内部 迭代:
- 外部:用户控制迭代(调用
++
要么 next()
)
- 内部:迭代器控制用户代码(请参阅
std::foreach
和 Iterator::foreach
)。
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中,迭代器就像一个切片。迭代器指的是a 序列 可以使用the从迭代器访问对象和元素 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#中称为“枚举器”)。
脚注
*:从技术上讲,这不是真的。你只需要在C ++中有一个迭代器,但是,通常有一对和库函数通常在迭代器对上运行(因此如果你想使用这些函数,你需要“两个迭代器”)。你有一个(开始,结束)对的事实并不意味着序列是有界的,结束迭代器可以无限远。可以把它想象成数学中的范围(0,∞)...∞实际上不是一个数字,它只是一个占位符,可以让你知道范围是无限的。
†:请记住,仅仅因为C ++中存在“end”迭代器并不意味着序列实际上已经结束了。 C ++中的某些结束迭代器就像无穷大。它们没有指向有效元素,无论你向前迭代多少次,你都无法达到无穷大。在Rust中,等效构造是一个永不返回的迭代器 None
。
我看到这里发生了三件事。让我们分解吧。
迭代器的想法
当你打电话给C ++时 std::begin
和鲁斯特 .iter()
在您的示例中,您将收到两个概念相同的“对象类型”:迭代器。
如果我们暂时忘记了实现细节,我们可以看到迭代器的目的和可用性恰好在两种语言中都是相似的。我们发现两个迭代器:
- 是否可以从集合中创建“对象”(“可迭代类型”)
- 可以使用C ++进行推进
std::advance
和鲁斯特 .next()
- 有一个“结束”,由C ++决定
std::end
和Rust的输出 .next()
。
当然,这是一个粗略的过度简化,它们在许多其他方面是相似和不同的,但这可能是您正在寻找的一般概述。
迭代器的实现
尽管有共同的主题,但C ++和Rust是非常不同的语言,并且自然会以不同的方式实现单一的想法。迭代器也不例外。
“为什么”太宽泛,无法在Stack Overflow上真正回答。这就像问为什么橙子是橙色而香蕉不是:)
但鉴于您的评论,您似乎对如何使用Rust的迭代器实现感到有些困惑:
我在Rust文档中找不到任何指针的确切定义
不要像现在的C ++程序员那样思考。查看 书 如果你还没有探索借贷和所有权的概念;它是远远更多 典型 您将使用数据的方式,并且需要了解Rust的迭代器如何工作。
迭代器的句法糖
都 C ++和Rust在他们的作品中有“魔力” for
循环使您可以轻松地使用迭代器“类型”。
与您的问题相反,这不是Rust特有的概念。在C ++中,对象可以与现代对象一起使用 for (item : collection)
语法,如果它 实现特殊方法,类似于 Iterator
你指出的特质。
概要
有哪些主要区别?
概念上并不多。
为什么Rust以这种方式拥有迭代器,为什么它们表达方式如此不同?
它就像是因为它。它们比你相信的更相似。
在C ++中是否有Rust类型的迭代器?是否有C ++ - 在Rust中键入迭代器?
它们在概念上是相同的。
它们被称为具体的东西吗? (内部外部?)
实施差异可能有一些奇特的学术术语,但我不知道。迭代器是一个迭代器。