Предположим, что у меня есть 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
не определено, но там какие-либо другие опасности или неэффективность с этим?
Версия reverse_iterator
показывает намерение и работает во всех контейнерах, независимо от их содержимого.
В первом есть тот недостаток, который вы описываете. Он также использует > =
, который не будет работать для итераторов без произвольного доступа.
Третья проблема состоит в том, что i
является int
. Он не сможет вместить столько, сколько потенциально может вернуть size ()
. Сделать его беззнаковым работает ( vector
), но тогда у нас есть та же проблема, что и у первого решения. ( 0U - 1
-> Фанки завершающие проверки
-> : |
)
Лично я бы выбрал второй.
Как вы указываете, первый требует, чтобы вы заключили цикл в if (! Myvec.empty ())
, чтобы избежать неопределенного поведения.
Для последнего вам, вероятно, следует использовать vector
или size_t
, и в этом случае > = 0
будет неправильно, вам нужно будет сделать ! = (size_t) -1
или аналогичный.
Таким образом, версия reverse_iterator
более чистая.
Что касается первой версии, вы также неизбежно закончите тем, что уменьшите итератор begin ()
в конце цикла (неопределенное поведение).
Для этого был создан reverse_iterator
.
Третье могло бы работать несколько лучше, если бы вы использовали несколько более спорную форму:
for (size_t i = vec.size(); i --> 0; )
Это могло бы быть идиомой, если бы люди перестали сопротивляться. Он использует подходящий тип счетчика (беззнаковый) и содержит мнемонику для легкого запоминания и распознавания.
Есть четвертый вариант (не обязательно хороший вариант, но он существует). Вы можете использовать итераторы двунаправленного / произвольного доступа таким образом, чтобы имитировать реализацию обратных итераторов, чтобы избежать проблемы с 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 );
Всегда используйте второй. Первый вы исключили сами, а третий не работает для списков и тому подобного.
Как правило, ничего из вышеперечисленного. Вместо этого обычно следует сесть и расслабиться на несколько секунд, выяснить, какой алгоритм вы хотите применить, и вообще забыть о написании цикла. Есть шанс, что вы будете использовать reverse_iterator
вместе с ним, но в зависимости от того, чего вы пытаетесь достичь, это не всегда так (например, см. std::copy_backwards
).