【问题标题】:Have range-based for loop start at point in vector让基于范围的 for 循环从向量中的点开始
【发布时间】:2014-05-16 13:31:23
【问题描述】:

我有一个 std::vector<MyClass*> container 指向某个类的对象的指针,并且想使用基于范围的 for 循环来迭代向量,就像这样

for (MyClass *item : container)
{
    // Do stuff, not changing the container
}

在这个循环中,我想再循环一次容器,但从容器中的下一个元素开始。如果没有迭代器,我真的找不到这样做的方法,所以我的解决方案是只使用那些来代替

for (auto item1 = container.begin(); item1 != container.end(); ++item1)
{
    for (auto item2 = item1.next(); item2 != container.end(); ++item2)
    {
        // Do stuff, not changing the container
    }
}

我的问题是:有没有办法做到这一点,而不必诉诸迭代器?我意识到基于范围的 for 循环只是语法糖,并且确实使用迭代器,但我喜欢我的语法糖!

【问题讨论】:

  • 如果没有迭代器,您将无法做到这一点。但是您可以避免放弃迭代整个集合的语法糖,请参阅this answer
  • 谢谢,看起来很有趣。
  • @user2079303 添加它作为答案,我会接受它。
  • 见我的回答附录

标签: c++ c++11 for-loop vector iterator


【解决方案1】:

(请注意下面的编辑)

for-range 循环不允许您访问内部迭代器。一方面,这很好,因为你不能乱用它。另一方面,这可能很糟糕,因为您需要迭代器知道您在容器中的位置。因此,我们不能使用 for-range 循环作为外循环,除非您使用线性内存布局迭代某些内容,在其中您可以将项目的地址作为迭代器。

此外,标准库还不能以方便的方式处理范围。我正在使用来自 Boost 的一些实用程序,例如 iterator_range 用于此代码 sn-p:

using boost::make_iterator_range;
using std::next;
for (auto iter1 = container.begin(), ee = container.end(); iter1 != ee; ++iter1) {
    auto& item1 = *iter1;
    for (auto& item2 : make_iterator_range(next(iter1),ee)) {
        // do something with item1 and item2
    }
}

诚然,不是很漂亮。但这显示了在给定一对迭代器的情况下如何使用 for-range 循环的一种方式。基本上,for-range 循环会吃掉任何提供开始/结束功能的东西。 make_iterator_range 包装了一对迭代器并返回一些提供开始/结束功能的东西。

编辑:我最近了解到boost::irange 也可以生成迭代器序列(而不仅仅是数字序列)。考虑到这一点,看看这个程序:

#include <iostream>
#include <vector>
#include <boost/range/irange.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/range/adaptor/indexed.hpp>

int main()
{
    using std::vector;
    using std::next;
    using boost::irange;
    using boost::make_iterator_range;
    namespace ba = boost::adaptors;

    vector<double> x {1.1, 2.2, 3.3, 4.4, 5.5, 6.6};

    for (auto iter1 : irange(begin(x),end(x))) {
        auto& item1 = *iter1;
        for (auto& item2 : make_iterator_range(next(iter1),end(x))) {
            // do something with item1 and item2
            std::cout << item1 << " < " << item2 << std::endl;
        }
    }

    return 0;
}

我在 C++0x 模式下使用 G++ 4.6.3 和 Boost 1.48 对此进行了测试,它确实有效。

【讨论】:

  • 好答案!虽然我不是很喜欢 Boost,但希望这样的东西能把它变成 C++。
【解决方案2】:

应 OP 的要求添加了我的评论作为答案。

如果没有迭代器,您将无法做到这一点。但是您可以避免放弃迭代整个集合的语法糖,请参阅this answer。我尚未测试该代码,但对该答案的评论表明 boost::counting_range 可能会更好。

【讨论】:

    【解决方案3】:

    您可以创建一个简单的类来访问该范围:

    #include <vector>
    #include <iostream>
    
    template<typename I>
    struct Sub {
        I _begin;
        I _end;
    
        Sub(I b, I e): _begin(b), _end(e) {}
    
        I begin() const { return _begin; }
        I end() const { return _end; }
    };
    
    template<typename I>
    Sub<I> sub(I b, I e) { return Sub<I>(b, e); }
    
    int main() {
        std::vector<int> a{0,1,2,3,4,5,6,7,8,9};
    
        for (auto i: sub(a.begin() + 3, a.end() - 1)) {
            std::cout << i << "\n";
        }
    }
    

    此代码打印:

    3
    4
    5
    6
    7
    8
    

    【讨论】:

      【解决方案4】:

      for循环的范围迭代容器中的所有元素。这意味着您可以将该循环更改为:

      for (auto item1 : container )
      {
          for (auto item2 = item1.next(); item2 != container.end(); ++item2)
          {
              // Do stuff, not changing the container
          }
      }
      

      由于内部循环不会遍历整个容器,因此您必须使用普通的 for 循环。

      【讨论】:

      • @ikh 没有人说item1 是一个迭代器。它是容器中元素的副本(MyClass*)。因此,我不确定我是否理解您的评论。
      • 我在示例中使用的函数next()std::next()MyClass* 既没有实现也不能实现它,因为next() 应该返回一个指向下一个的迭代器容器中的元素。容器中的对象对容器一无所知,因此不可能从中获取迭代器。
      猜你喜欢
      • 1970-01-01
      • 2020-05-03
      • 2021-12-18
      • 2016-10-31
      • 2021-04-08
      • 2013-01-04
      相关资源
      最近更新 更多