【问题标题】:Iterating C++ vector from the end to the beginning从头到尾迭代 C++ 向量
【发布时间】:2011-04-06 08:56:06
【问题描述】:

是否可以从头到尾迭代一个向量?

for (vector<my_class>::iterator i = my_vector.end();
        i != my_vector.begin(); /* ?! */ ) {
}

或者只有这样的事情才有可能:

for (int i = my_vector.size() - 1; i >= 0; --i) {
}

【问题讨论】:

  • 在 C++11 中,您可以使用基于范围的 for 循环和反向适配器,see here
  • 理论上,在 32 位机器上,对于第二种解决方案,如果向量大小大于 2,147,483,647 + 1 它将溢出(vector::size() 是无符号的),但目前的可能性是你永远不会达到这个限制(32 位机器上的当前向量限制也是 1,073,741,823)。
  • @StefanRogin 溢出问题变得真实,而不是 for 循环中的“int i”有人在寻求避免编译器警告时使用 size_t(或者可能是 auto)(由于 size() 分配给 int) .这样,对于单个元素向量,第二次迭代会溢出 auto i 并且循环执行时溢出的“i”会导致各种崩溃。

标签: c++ vector iterator


【解决方案1】:

一种方法是:

for (vector<my_class>::reverse_iterator i = my_vector.rbegin(); 
        i != my_vector.rend(); ++i ) { 
} 

rbegin()/rend() 专为此目的而设计。 (是的,增加 reverse_interator 会将其向后移动。)

现在,理论上,您的方法(使用 begin()/end()--i)可以工作,std::vector 的迭代器是双向的,但请记住,end() 不是最后一个元素 - 它是一个超出最后一个元素,所以你必须先递减,当你到达begin()时你就完成了——但你仍然需要进行处理。

vector<my_class>::iterator i = my_vector.end();
while (i != my_vector.begin())
{
     --i;
    /*do stuff */

} 

更新:在将for() 循环重写为while() 循环时,我显然过于激进了。 (重要的是--i在开头。)

【讨论】:

  • 我刚刚意识到如果容器为空,--i 将导致一个大问题......在进入do - while 循环之前检查(my_vector.begin() != my_vector.end()) 是有意义的。
  • 你为什么使用do-while循环而不是while循环?那么你就不需要对空向量进行任何特殊检查了。
  • 您能否更新答案以使用auto 以获得更好的可读性?
【解决方案2】:

由于我不想引入类似外星人的新 C++ 语法,而我只是想在现有原语的基础上进行构建,因此下面的 sn-ps 似乎可以工作:

#include <vector>
#include <iostream>

int main (int argc,char *argv[])
{
    std::vector<int> arr{1,2,3,4,5};
    std::vector<int>::iterator it;

    // iterate forward
    for (it = arr.begin(); it != arr.end(); it++) {
        std::cout << *it << " ";
    }

    std::cout << "\n************\n";
 
    if (arr.size() > 0) {
        // iterate backward, simple Joe version
        it = arr.end() - 1;
        while (it != arr.begin()) {
            std::cout << *it << " ";
            it--;
        }
        std::cout << *it << " ";
    } 

    // iterate backwards, the C++ way
    std::vector<int>::reverse_iterator rit;
    for (rit = arr.rbegin(); rit != arr.rend(); rit++) {
        std::cout << *rit << " ";
    }

    return 0;
}

【讨论】:

  • 如果arr 指的是一个空向量,这段代码将非常失败!
【解决方案3】:

如果您可以使用 Boost 库,则有 Boost.Range 提供 reverse range adapter,包括:

#include <boost/range/adaptor/reversed.hpp>

然后,结合C++11's range-for loop,您只需编写以下内容:

for (auto& elem: boost::adaptors::reverse(my_vector)) {
   // ...
}

由于此代码比使用迭代器对的代码更简洁,因此它可能更具可读性且不易出错,因为需要注意的细节较少。

【讨论】:

  • 确实,boost::adaptors::reverse 很有用!
【解决方案4】:

从c++20开始,可以使用std::ranges::reverse_view和基于范围的for循环:

#include<ranges>
#include<vector>
#include<iostream>

using namespace std::ranges;

std::vector<int> const vec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

for(auto& i :  views::reverse(vec)) {
    std::cout << i << ",";
}

甚至

for(auto& i :  vec | views::reverse)

不幸的是,在撰写本文时(2020 年 1 月),没有主要编译器实现范围库,但您可以求助于 Eric Niebler's ranges-v3

#include <iostream>
#include <vector>
#include "range/v3/all.hpp"

int main() {

    using namespace ranges;

    std::vector<int> const vec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    for(auto& i :  views::reverse(vec)) {
        std::cout << i << ",";
    }

    return 0;
}

【讨论】:

  • 我对@9​​87654327@ 这一行感到困惑。它是如何工作的? | 在这里做什么?
  • @DinoSaric 这是 C++20 中的一项新功能,允许对范围进行组合操作。例如看这个教程:hannes.hauswedell.net/post/2019/11/30/range_intro
【解决方案5】:

通过封闭开放范围进行反向迭代的成熟“模式”如下所示

// Iterate over [begin, end) range in reverse
for (iterator = end; iterator-- != begin; ) {
  // Process `*iterator`
}

或者,如果你愿意,

// Iterate over [begin, end) range in reverse
for (iterator = end; iterator != begin; ) {
  --iterator;
  // Process `*iterator`
}

这种模式很有用,例如,用于使用无符号索引对数组进行反向索引

int array[N];
...
// Iterate over [0, N) range in reverse
for (unsigned i = N; i-- != 0; ) {
  array[i]; // <- process it
}

(不熟悉这种模式的人经常坚持使用 signed 整数类型进行数组索引,特别是因为他们错误地认为无符号类型在某种程度上“无法用于”反向索引)

它可用于使用“滑动指针”技术迭代数组

// Iterate over [array, array + N) range in reverse
for (int *p = array + N; p-- != array; ) {
  *p; // <- process it
}

或者它可以用于使用普通(非反向)迭代器对向量进行反向迭代

for (vector<my_class>::iterator i = my_vector.end(); i-- != my_vector.begin(); ) {
  *i; // <- process it
}

【讨论】:

  • cppreference.com 说,在 end() 访问元素“导致未定义的行为”,所以我认为,循环应该从 --end()
  • @ThomasSchmid 这些循环从不尝试访问end()。尽管它们似乎从 end() 开始,但它们始终确保在第一次访问之前递减迭代器。
  • 这比 rbegin/rend 好得多,因为你可以在运行时循环其他方式(无模板)auto a = vector&lt;int&gt;{0,1,2}; bool reversed = 0; auto it = (!reversed?a.begin():a.end()); auto end = (reversed?a.begin():a.end());while(it != end) { if(reversed)--it; cout &lt;&lt; *it &lt;&lt; endl; if(!reversed)++it; }
  • @colin Egads!那个丑!您正在测试reversed 四次 次——其中两次在一个循环中。当然,测试一个布尔值是非常快的,但是,为什么你不需要工作呢?特别是,因为唯一的目的似乎是使代码不可读。我们如何使用两个单独的循环? if (reversed) for (auto it = my_vector.rbegin(); it != my_vector.rend(); ++it) {doStuff(*it);} else for (auto it = my_vector.begin(); it != my_vector.end(); ++it) {doStuff(*it);}
  • 其实你错过了我的意思。您将它分成两个ifs 是绝对正确的,但我想摆脱doStuff() 上的模板。仍然可以使用两个ifs,通过在第一个循环上循环。
【解决方案6】:

如果你有 C++11,你可以使用auto

for (auto it = my_vector.rbegin(); it != my_vector.rend(); ++it)
{
}

【讨论】:

    【解决方案7】:

    这是一个超级简单的实现,它允许使用 for each 构造并且仅依赖于 C++14 标准库:

    namespace Details {
    
        // simple storage of a begin and end iterator
        template<class T>
        struct iterator_range
        {
            T beginning, ending;
            iterator_range(T beginning, T ending) : beginning(beginning), ending(ending) {}
    
            T begin() const { return beginning; }
            T end() const { return ending; }
        };
    
    }
    
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    // usage:
    //  for (auto e : backwards(collection))
    template<class T>
    auto backwards(T & collection)
    {
        using namespace std;
        return Details::iterator_range(rbegin(collection), rend(collection));
    }
    

    这适用于提供 rbegin() 和 rend() 的东西,以及静态数组。

    std::vector<int> collection{ 5, 9, 15, 22 };
    for (auto e : backwards(collection))
        ;
    
    long values[] = { 3, 6, 9, 12 };
    for (auto e : backwards(values))
        ;
    

    【讨论】:

      【解决方案8】:

      我喜欢 Yakk 结尾处的反向迭代器 - Adam Nevraumont 的回答,但对于我需要的东西来说似乎很复杂,所以我写了这个:

      template <class T>
      class backwards {
          T& _obj;
      public:
          backwards(T &obj) : _obj(obj) {}
          auto begin() {return _obj.rbegin();}
          auto end() {return _obj.rend();}
      };
      

      我可以像这样使用普通的迭代器:

      for (auto &elem : vec) {
          // ... my useful code
      }
      

      并将其更改为 this 以反向迭代:

      for (auto &elem : backwards(vec)) {
          // ... my useful code
      }
      

      【讨论】:

        【解决方案9】:
        template<class It>
        std::reverse_iterator<It> reversed( It it ) {
          return std::reverse_iterator<It>(std::forward<It>(it));
        }
        

        然后:

        for( auto rit = reversed(data.end()); rit != reversed(data.begin()); ++rit ) {
          std::cout << *rit;
        

        或者在 C++14 中做:

        for( auto rit = std::rbegin(data); rit != std::rend(data); ++rit ) {
          std::cout << *rit;
        

        在 C++03/11 中,大多数标准容器也有 .rbegin().rend() 方法。

        最后,你可以编写范围适配器backwards如下:

        namespace adl_aux {
          using std::begin; using std::end;
          template<class C>
          decltype( begin( std::declval<C>() ) ) adl_begin( C&& c ) {
            return begin(std::forward<C>(c));
          }
          template<class C>
          decltype( end( std::declval<C>() ) ) adl_end( C&& c ) {
            return end(std::forward<C>(c));
          }
        }
        
        template<class It>
        struct simple_range {
          It b_, e_;
          simple_range():b_(),e_(){}
          It begin() const { return b_; }
          It end() const { return e_; }
          simple_range( It b, It e ):b_(b), e_(e) {}
        
          template<class OtherRange>
          simple_range( OtherRange&& o ):
            simple_range(adl_aux::adl_begin(o), adl_aux::adl_end(o))
          {}
        
          // explicit defaults:
          simple_range( simple_range const& o ) = default;
          simple_range( simple_range && o ) = default;
          simple_range& operator=( simple_range const& o ) = default;
          simple_range& operator=( simple_range && o ) = default;
        };
        template<class C>
        simple_range< decltype( reversed( adl_aux::adl_begin( std::declval<C&>() ) ) ) >
        backwards( C&& c ) {
          return { reversed( adl_aux::adl_end(c) ), reversed( adl_aux::adl_begin(c) ) };
        }
        

        现在你可以这样做了:

        for (auto&& x : backwards(ctnr))
          std::cout << x;
        

        我觉得挺好看的。

        【讨论】:

          【解决方案10】:

          使用此代码

          //print the vector element in reverse order by normal iterator.
          cout <<"print the vector element in reverse order by normal iterator." <<endl;
          vector<string>::iterator iter=vec.end();
          --iter;
          while (iter != vec.begin())
          {
              cout << *iter  << " "; 
              --iter;
          }
          

          【讨论】:

          • 如果vec 指的是一个空向量,这段代码将非常失败!
          【解决方案11】:

          使用反向迭代器并从rbegin() 循环到rend()

          【讨论】:

            【解决方案12】:

            用户rend() / rbegin()迭代器:

            for (vector&lt;myclass&gt;::reverse_iterator it = myvector.rbegin(); it != myvector.rend(); it++)

            【讨论】:

              猜你喜欢
              • 2020-11-01
              • 1970-01-01
              • 2013-12-16
              • 1970-01-01
              • 1970-01-01
              • 2012-10-08
              • 1970-01-01
              • 2016-01-10
              • 1970-01-01
              相关资源
              最近更新 更多