Итерация назад

Предположим, что у меня есть a vector<int> myvec и я хочу циклично выполниться через все элементы наоборот. Я могу думать о нескольких способах сделать это:

for (vector<int>::iterator it = myvec.end() - 1; it >= myvec.begin(); --it)
{
    // do stuff here
}

for (vector<int>::reverse_iterator rit = myvec.rbegin(); rit != myvec.rend(); ++rit)
{
    // do stuff here
}

for (int i = myvec.size() - 1; i >= 0; --i)
{
    // do stuff here
}

Таким образом, мой вопрос состоит в том, когда я должен использовать каждого? Существует ли различие? Я знаю, что первый опасен потому что, если я передаю в пустом векторе, затем myvec.end() - 1 не определено, но там какие-либо другие опасности или неэффективность с этим?

5
задан MBennett 30 March 2010 в 21:33
поделиться

6 ответов

Версия reverse_iterator показывает намерение и работает во всех контейнерах, независимо от их содержимого.

В первом есть тот недостаток, который вы описываете. Он также использует > = , который не будет работать для итераторов без произвольного доступа.

Третья проблема состоит в том, что i является int . Он не сможет вместить столько, сколько потенциально может вернуть size () . Сделать его беззнаковым работает ( vector :: size_type ), но тогда у нас есть та же проблема, что и у первого решения. ( 0U - 1 -> Фанки завершающие проверки -> : | )

11
ответ дан 18 December 2019 в 06:22
поделиться

Лично я бы выбрал второй.

Как вы указываете, первый требует, чтобы вы заключили цикл в if (! Myvec.empty ()) , чтобы избежать неопределенного поведения.

Для последнего вам, вероятно, следует использовать vector :: size_type или size_t , и в этом случае > = 0 будет неправильно, вам нужно будет сделать ! = (size_t) -1 или аналогичный.

Таким образом, версия reverse_iterator более чистая.

3
ответ дан 18 December 2019 в 06:22
поделиться

Что касается первой версии, вы также неизбежно закончите тем, что уменьшите итератор begin () в конце цикла (неопределенное поведение).

Для этого был создан reverse_iterator .

Третье могло бы работать несколько лучше, если бы вы использовали несколько более спорную форму:

for (size_t i = vec.size(); i --> 0; )

Это могло бы быть идиомой, если бы люди перестали сопротивляться. Он использует подходящий тип счетчика (беззнаковый) и содержит мнемонику для легкого запоминания и распознавания.

2
ответ дан 18 December 2019 в 06:22
поделиться

Есть четвертый вариант (не обязательно хороший вариант, но он существует). Вы можете использовать итераторы двунаправленного / произвольного доступа таким образом, чтобы имитировать реализацию обратных итераторов, чтобы избежать проблемы с myvec.end () - 1 на пустом итераторе:

for (vector<int>::iterator it = myvec.end(); it != myvec.begin(); --it)
{
    // convert the loop controlling iterator to something that points
    //  to the item we're really referring to

    vector<int>::iterator true_it = it;
    --true_it;


    // do stuff here
    //  but always dereference `true_it` instead of `it`
    //  this is essentially similar to the way a reverse_iterator 
    //  generally works

    int& x = *true_it;
}

или даже:

for (vector<int>::iterator it = myvec.end(); it != myvec.begin();)
{
    // decrement `it` before the loop executes rather than after
    //  it's a bit non-idiomatic, but works
    --it;

    int& x = *it;

    // do stuff...
}

Как я уже сказал, это не обязательно хороший вариант (я думаю, что ответ Джерри Коффина - это подход, на который вы должны обратить внимание в первую очередь), но я думаю, что он интересен, поскольку он показывает, как обратные итераторы работают за кулисами - и это позволяет избежать преобразования reverse_iterator в итератор в тех случаях, когда вы можете использовать итератор с чем-то, что не принимает reverse_iterator (преобразование reverse_iterator в ] iterator s всегда, кажется, у меня болит голова, поэтому я часто избегаю reverse_iterators , чтобы избежать головной боли). Например, если вы хотите вызвать insert () для местоположения, обратный итератор ссылается на:

// if `it` is a reverse iterator, a call to insert might have to look something like:

myvec.insert( --(it.base()), 42 );  // assume that you know the current vector capacity
                                    //  will avoid a reallocation, so the loop's 
                                    //  iterators won't be invalidated

// if `it` is a normal iterator...

myvec.insert( it, 42 );
1
ответ дан 18 December 2019 в 06:22
поделиться

Всегда используйте второй. Первый вы исключили сами, а третий не работает для списков и тому подобного.

1
ответ дан 18 December 2019 в 06:22
поделиться

Как правило, ничего из вышеперечисленного. Вместо этого обычно следует сесть и расслабиться на несколько секунд, выяснить, какой алгоритм вы хотите применить, и вообще забыть о написании цикла. Есть шанс, что вы будете использовать reverse_iterator вместе с ним, но в зависимости от того, чего вы пытаетесь достичь, это не всегда так (например, см. std::copy_backwards).

7
ответ дан 18 December 2019 в 06:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: