问题 Rust Iterator和C ++ Iterator之间的主要区别是什么? [关闭]


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 特征,似乎迭代器是容器的包装器,通过以某种方式标准化逻辑(如果这样做有意义),可以更容易地处理。

我的主要问题是:

  1. 有哪些主要区别?
  2. 为什么Rust以这种方式拥有迭代器,为什么它们表达方式如此不同?
  3. 在C ++中是否有Rust类型的迭代器?
  4. 是否有C ++ - 在Rust中键入迭代器?
  5. 它们被称为具体的东西吗? (内部外部?)

6618
2018-02-27 01:33


起源

两种不同的语言,不同的语法等 - PaulMcKenzie
Python的方法与Rust的方法非常相似。你在迭代器中包含了更多的逻辑,因此它知道它何时结束而不需要将它与sentinel值(C ++中的结束迭代器)进行比较。 C ++的方法可能有点单调乏味,因为实用程序函数最终需要为开始和结束迭代器提供单独的参数,而不是简单地接收知道自己的终点的迭代器。 - ShadowRanger
相当于C ++代码的Rust等价物 let v = vec![1, 2, 3, 4]; for i in &v { println!("{},", i); } 注意缺少电话 next。您可能希望进一步学习Rust;这些类型的问题随着基线知识的增加而逐渐消失。 - Shepmaster
那么在C ++中是否存在这些类型的迭代器,因为我同意@ShadowRanger,这是相当繁琐的,我认为Rust的方法更好......但我认为不是这种情况并不是很多函数需要分开的有开始和结束迭代器...我也研究了一下,看到这些可能被称为内部和外部迭代器......但这是完全正确的吗? - Hacksaurus_Babu
C ++ 11增加了范围: en.cppreference.com/w/cpp/language/range-for 至少在正确的方向上移动。 - loganfsmyth


答案:


迭代器是编程语言中的一个概念,用于指代允许的构造 迭代 过度集合或元素序列。这个概念是故意模糊的,这是一个概念!它没有规定任何具体的实施。

为了更容易区分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)。

15
2018-02-27 07:28





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


1
2018-02-27 03:54



@Tim:完全相同的东西适用于C ++。只是因为 end 分配值并不意味着您可以通过访问元素 end。有关简单的具体示例,请参阅 std::forward_list除了将它与其他迭代器进行比较之外,对于最终迭代器几乎没有什么可以做的。另一个例子是 std::istream_iterator,它实际上只是从流中读取值直到流结束。该 end 可能有一个值,但它不会给你任何额外的功能。 - Dietrich Epp
@Tim:同样,在C ++中也是如此。唯一的要求是你可以比较你的 start 和 end 迭代器。没有要求他们将比较平等。 - Dietrich Epp
@Tim:基本Iterator概念的文档可以在这里找到: en.cppreference.com/w/cpp/concept/Iterator。本文档可能会澄清您对C ++迭代器如何工作的任何问题。 - Dietrich Epp
@Tim:就像在Rust中一样,并非所有迭代器都是由容器构成的,在C ++中,并非所有迭代器都来自 std::begin() 和 std::end()。此外,无法保证 std::end() 已定义,如果已定义,则无法保证 std::end() 可以从 std::begin()。没有真正的“独特” iterator 和 iterator concept,“iterator”一词指的是符合迭代器概念的对象。 - Dietrich Epp
成本是你只限于Rust中的一个(可能是双端)迭代器  - 这是 不对。也许你的意思更微妙? - Shepmaster


我看到这里发生了三件事。让我们分解吧。

迭代器的想法

当你打电话给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中键入迭代器?

它们在概念上是相同的。

它们被称为具体的东西吗? (内部外部?)

实施差异可能有一些奇特的学术术语,但我不知道。迭代器是一个迭代器。


-2
2018-02-27 02:30



从概念上讲,C ++迭代器与Rust中的迭代器有很大不同。 C ++中的迭代器更像是通用指针,你可以编写像 std::sort 要么 std::find_if。在Rust中,您根本不能使用迭代器来表示范围,因此必须重写C ++中的许多这些技术。 - Dietrich Epp
在问题的编辑之前,似乎缺少了迭代器的基本心理概念;将事情降低到基本水平是合适的。我同意这个答案现在过于简单了。 - Litty
@Litty,你的答案不是太简单,差异是关于语言语法,而不是迭代器概念。这两种语言中同一概念的实现反映了这两种语言的意图:避免了Rust错误,在C ++中可以获得最佳效率。 - Oliv
@Oliv:你能举个例子说明在C ++中可以实现最佳效率而不是Rust吗? - Matthieu M.
@DietrichEpp可以扩展您的意思 在Rust中,您无法使用迭代器来表示范围?例如,这个 我会考虑迭代一系列的集合。您还可以直接迭代范围(for i in 0..10 {})。 - Shepmaster