Итерация вектора C ++ от конца к началу


101

Можно ли перебрать вектор от конца до начала?

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

Или это возможно только с чем-то вроде этого:

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

2
В C ++ 11 вы можете использовать цикл for на основе диапазона с обратным адаптером, см. Здесь
MM

1
теоретически на 32-битной машине для второго решения, если размер вектора больше 2147483647 + 1, он будет переполняться (vector :: size () беззнаковый), но в настоящее время есть вероятность, что вы никогда не достигнете этого предела (также текущий предел вектора на 32-битных машинах составляет 1 073 741 823).
Стефан Рогин

Проблема переполнения @StefanRogin становится реальной, когда вместо int i в цикле for кто-то использует size_t (или, возможно, auto) в своем поиске, чтобы избежать предупреждений компилятора (из-за присвоения size () int). При этом и для вектора с одним элементом вторая итерация переполняет auto i, и цикл выполняется с переполненным «i», что приводит к всевозможным сбоям.
n-mam

Ответы:


164

Лучший способ:

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

rbegin()/ rend()были специально разработаны для этой цели. (И да, при увеличении a 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()).
a1ex07

1
Почему вы используете do-whileцикл, а не просто whileцикл? Тогда вам не понадобится специальная проверка на пустые векторы.
jamesdlin

Не могли бы вы обновить ответ, чтобы использовать его autoдля лучшей читаемости?
LNJ

60

Если у вас есть C ++ 11, вы можете использовать auto.

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

30

Хорошо зарекомендовавший себя «шаблон» для обратного перебора диапазонов закрыто-открыто выглядит следующим образом.

// 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
}

(Люди , незнакомые с этой моделью часто настаивают на использование подписали целое число типов для индексации массивов именно потому , что они ошибочно полагают , что беззнаковые типы, так или иначе «непригодные» для обратной индексации)

Его можно использовать для перебора массива с помощью техники «скользящего указателя».

// 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()
Thomas Schmid

1
@ThomasSchmid Эти циклы никогда не пытаются получить доступ к end(). Даже если кажется, что они начинаются с end(), они всегда убирают итератор перед первым доступом.
AnT 05

Это намного лучше, чем rbegin / rend, потому что вы можете зацикливать другой путь во время выполнения (без шаблона) auto a = vector<int>{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 << *it << endl; if(!reversed)++it; }
Колин

1
@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);}
Джеймс Карран

На самом деле вы упустили мою точку зрения. Вы абсолютно правы, разделив его на две части, ifно я хотел избавиться от шаблона на doStuff(). Тем не менее, это выполнимо с двумя ifs, которые у вас есть, путем обратного цикла на первом.
Colin

17

Начиная с 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 г.) ни один крупный компилятор не реализует библиотеку диапазонов, но вы можете прибегнуть к диапазонам Эрика Ниблера-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

Пользовательские rend() / rbegin()итераторы:

for (vector<myclass>::reverse_iterator it = myvector.rbegin(); it != myvector.rend(); it++)


6
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;

В качестве альтернативы в С ++ 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;

что я считаю довольно красивым.



1

Мне нравится обратный итератор в конце ответа Якка - Адама Неврамонта, но он казался сложным для того, что мне было нужно, поэтому я написал это:

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
}

и измените его на это, чтобы выполнить итерацию в обратном порядке:

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

1

Вот суперпростая реализация, которая позволяет использовать для каждой конструкции и полагается только на стандартную библиотеку 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))
    ;

1

Если вы можете использовать библиотеку Boost, есть Boost.Range, который предоставляет reverseадаптер диапазона , включая:

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

Затем в сочетании с циклом диапазона C ++ 11for вы можете просто написать следующее:

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

Поскольку этот код короче, чем код, использующий пару итераторов, он может быть более читабельным и менее подверженным ошибкам, так как меньше деталей, на которые следует обращать внимание.


1
Действительно, boost::adaptors::reverseочень полезно!
Кай Петцке,

-1

используйте этот код

//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;
}

1
Этот код ужасно не работает, если vecссылается на пустой вектор!
Кай Петцке,

-2

Поскольку я не хочу вводить новый синтаксис C ++, похожий на пришельцев, и я просто хочу развить существующие примитивы, приведенные ниже фрагменты, похоже, работают:

#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ссылается на пустой вектор!
Кай Петцке,
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.