Навіщо використовувати ітератори замість масивів індексів?


239

Візьміть наступні два рядки коду:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

І це:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

Мені кажуть, що другий спосіб є кращим. Чому саме це?


72
Другий спосіб кращий змінити some_iterator++в ++some_iterator. Поступове збільшення створює непотрібний тимчасовий ітератор.
Ясон

6
Ви також повинні внести end()до декларації пункт.
Гонки легкості по орбіті

5
@Tomalak: кожен, хто використовує C ++ з неефективною реалізацією, vector::endмабуть, має турбуватися гірші проблеми, ніж те, чи вона піднята з циклів чи ні. Особисто я віддаю перевагу ясності - якщо б це був дзвінок findв умові припинення, я б хвилювався.
Стів Джессоп

13
@Tomalak: Цей код не є неохайним (ну, може бути, після збільшення), він стислий і зрозумілий, наскільки ітератори C ++ дозволяють стислість. Додавання більшої кількості змінних додає пізнавальних зусиль заради передчасної оптимізації. Це неохайно.
Стів Джессоп

7
@Tomalak: це передчасно, якщо це не вузьке місце. Ваш другий пункт здається мені абсурдним, оскільки правильне порівняння не між it != vec.end()і it != end, це між (vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)і (vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it). Мені не потрібно рахувати символів. У будь-якому разі віддайте перевагу одне над іншим, але незгода інших людей з вашими уподобаннями не є "неохайністю", це перевагою більш простого коду з меншою кількістю змінних і, таким чином, менше думати, читаючи його.
Стів Джессоп

Відповіді:


210

Перша форма ефективна лише в тому випадку, якщо vector.size () - швидка операція. Це стосується, наприклад, векторів, але не для списків. Крім того, що ви плануєте робити в тілі петлі? Якщо ви плануєте доступ до елементів, як в

T elem = some_vector[i];

то ви робите припущення, що контейнер operator[](std::size_t)визначив. Знову це стосується векторних, але не для інших контейнерів.

Використання ітераторів наблизить до незалежності контейнера . Ви не робите припущення про можливість випадкового доступу або швидку size()роботу, лише що контейнер має можливості ітератора.

Ви можете додатково покращити свій код, використовуючи стандартні алгоритми. В залежності від того, що ви намагаєтеся досягти, ви можете вибрати для використання std::for_each(), std::transform()і так далі. Використовуючи стандартний алгоритм, а не явний цикл, ви уникаєте повторної винаходи колеса. Ваш код, швидше за все, буде більш ефективним (з урахуванням правильного алгоритму), правильним та багаторазовим.


8
Крім того, ви забули, що ітератори можуть виконувати такі дії, як невдала швидкість, так що якщо відбудеться одночасна модифікація структури, до якої ви отримуєте доступ, ви дізнаєтесь про це. Ви не можете зробити це лише з цілим числом.
Марцін

4
Це мене бентежить: "Це справедливо для векторів, але не для списків, наприклад". Чому? Кожен, хто має мозок, буде вести size_tзмінну члена, відстежуючи size().
GManNickG

19
@GMan - майже у всіх реалізаціях розмір () швидкий для списків так само, як і для векторів. Наступна версія стандарту вимагатиме, щоб це було правдою. Справжня проблема - це повільність відновлення за позицією.
Даніель Ервікер

8
@GMan: Для збереження розміру списку потрібно нарізання списків та сплайсинг бути O (n) замість O (1).

5
У C ++ 0x size()функція-член повинна мати постійну часову складність для всіх контейнерів, які підтримують її, в тому числі std::list.
Джеймс Мак-Нілліс

54

Це частина сучасного процесу індоктринації C ++. Ітератори - це єдиний спосіб ітерації більшості контейнерів, тому ви використовуєте його навіть з векторами, щоб просто ввійти в належний спосіб мислення. Серйозно, це єдина причина, що я роблю це - я не думаю, що я ніколи не замінював вектор іншим видом контейнера.


Ого, це все ще неприйнято через три тижні. Я здогадуюсь, це не платити, щоб бути маленьким язиком у щоці.

Я думаю, що індекс масиву є більш читабельним. Він відповідає синтаксису, який використовується в інших мовах, і синтаксису, який використовується для старомодних масивів C. Це також менш багатослівно. Якщо ваш компілятор є корисним, ефективність має бути достатньою, і навряд чи є випадки, коли це важливо.

Незважаючи на це, я все ще часто використовую ітератори з векторами. Я вважаю, що ітератор - це важлива концепція, тому я просуваю її, коли можу.


1
C ++ ітератори також жахливо розбиті концептуально. Щодо векторів я просто потрапив, тому що кінцевий покажчик акутно закінчується + 1 (!). Для потоків модель ітератора просто сюрреалістична - уявний маркер, який не існує. Так само і для пов'язаних списків. Парадигма має сенс лише для масивів, і то не дуже. Навіщо мені потрібні два ітераторні об’єкти, а не один ...
Tuntable

5
@aberglas вони зовсім не зламані, ти просто до них не звик, тому я раджу використовувати їх навіть тоді, коли не потрібно! Напіввідкриті діапазони є загальною концепцією, а дозорні, до яких ніколи не можна звернутися безпосередньо, приблизно такі ж старі, як і саме програмування.
Марк Рансом

4
подивіться на ітераторів потоку і подумайте, що == було зірвано для того, щоб відповідати шаблону, а потім скажіть мені, що ітератори не порушені! Або для пов'язаних списків. Навіть для масивів необхідність вказувати один минулий кінець - це зламана ідея стилю С - вказівник на те, що ніколи не буває. Вони повинні бути схожими на Java або C # або будь-яку іншу мову ітераторів, із необхідним одним ітератором (замість двох об’єктів) та простим кінцевим тестом.
Tuntable

53

тому що ви не прив'язуєте свій код до конкретної реалізації списку some_vector. якщо ви використовуєте індекси масиву, це повинен бути якийсь вид масиву; якщо ви використовуєте ітератори, ви можете використовувати цей код у будь-якій реалізації списку.


23
Інтерфейс std :: list навмисно не пропонує оператора [] (size_t n), оскільки це було б O (n).
MSalters

33

Уявіть, що деякий_вектор реалізований із пов'язаним списком. Тоді запит елемента на i-му місці вимагає виконання операцій i для переходу списку вузлів. Тепер, якщо ви використовуєте ітератор, взагалі кажучи, він докладе максимум зусиль, щоб бути максимально ефективним (у випадку пов'язаного списку він підтримуватиме вказівник на поточний вузол і просуває його у кожній ітерації, вимагаючи лише одна операція).

Тож передбачено дві речі:

  • Абстракція використання: ви просто хочете повторити деякі елементи, вам не байдуже, як це зробити
  • Продуктивність

1
"він підтримуватиме вказівник на поточний вузол і просуває його [хороший матеріал про ефективність]" - так, я не розумію, чому люди мають проблеми з розумінням поняття ітераторів. вони концептуально просто набір покажчиків. навіщо обчислювати зміщення деякого елемента знову і знову, коли ви можете просто кешувати на нього вказівник? ну, це теж роблять ітератори.
підкреслюй_d

27

Я буду тут захисником чортів, а не рекомендую ітераторів. Основна причина, чому я не потребую використання ітераторів, над якими я працював, від розробки додатків Desktop до розробки ігор. Постійно від них не потрібно, а по-друге, приховані припущення та безладдя коду та налагодження кошмарів, які ви отримуєте з ітераторами, роблять їх прекрасним прикладом не використовувати його в будь-яких програмах, які вимагають швидкості.

Навіть з точки зору обслуговування, вони безлад. Це не через них, а через все спотворення, яке відбувається за сценою. Як я знаю, що ви не реалізували свій власний список віртуальних векторів чи масивів, що робить щось зовсім інше, ніж стандарти. Чи знаю я, який тип зараз є під час виконання? Ви перевантажували оператора, я не встиг перевірити весь ваш вихідний код. Пекло, я навіть знаю, яку версію STL ви використовуєте?

Наступна проблема, яку ви маєте з ітераторами, - це нещільна абстракція, хоча є численні веб-сайти, які детально обговорюють це.

Вибачте, я ще не бачив жодного сенсу в ітераторах. Якщо вони абстрагують список або вектор далеко від вас, то насправді ви вже повинні знати, з яким вектором чи перелічуєте свої стосунки, якщо цього не зробите, тоді ви просто збираєтеся налаштувати себе на кілька великих сесій налагодження в майбутньому.


23

Ви можете скористатися ітератором, якщо ви збираєтесь додавати / видаляти елементи до вектора, поки ви перебираєте його.

some_iterator = some_vector.begin(); 
while (some_iterator != some_vector.end())
{
    if (/* some condition */)
    {
        some_iterator = some_vector.erase(some_iterator);
        // some_iterator now positioned at the element after the deleted element
    }
    else
    {
        if (/* some other condition */)
        {
            some_iterator = some_vector.insert(some_iterator, some_new_value);
            // some_iterator now positioned at new element
        }
        ++some_iterator;
    }
}

Якщо ви використовували індекси, вам доведеться переміщувати елементи вгору / вниз у масиві, щоб обробляти вставки та видалення.


3
якщо ви хочете вставити елементи в середину контейнера, то, можливо, вектор не є хорошим вибором контейнера для початку. звичайно, ми повернулися до того, чому ітератори круті; тривіально переходити до списку.
wilhelmtell

Ітерація над усіма елементами є досить дорогою std::listпорівняно з a std::vector, якщо ви рекомендуєте використовувати зв'язаний список замість std::vector. Див. Стор. 43: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf На моєму досвіді я знайшов a std::vectorшвидше, ніж std::listнавіть якщо я шукаю все це та видаляю елементи у довільних положеннях.
Девід Стоун

Індекси стабільні, тому я не бачу, які додаткові перемішування потрібні для вставки та видалення.
musiphil

... І з пов'язаним списком - що для цього слід використовувати - ваше твердження циклу було б for (node = list->head; node != NULL; node = node->next)коротшим, ніж ваші перші два рядки коду, складені разом (декларація та голова циклу). Тому я ще раз кажу - між принципами ітераторів та їх використанням не існує великої різниці - ви все ще задовольняєте трьом частинам forзаяви, навіть якщо ви використовуєте while: заявити, повторити, перевірити припинення.
Інженер

16

Розмежування проблем

Дуже приємно відокремити код ітерації від проблеми "основного" циклу. Це майже дизайнерське рішення.

Дійсно, ітерація за допомогою індексу прив'язує вас до реалізації контейнера. Попросивши контейнер для початку та кінця ітератора, він дозволяє цикл циклу для використання з іншими типами контейнерів.

Крім того std::for_each, ви сказали колекції, що робити, замість того, щоб запитати щось про її внутрішні

Стандарт 0x планує запровадити закриття, що зробить такий підхід набагато простішим у використанні - погляньте на виразну силу, наприклад, Ruby's [1..6].each { |i| print i; }...

Продуктивність

Але, можливо, набагато надзвичайна проблема полягає в тому, що, використовуючи for_eachпідхід, надається можливість паралелізації ітерації - блоки, що нарізують Intel, можуть розподіляти блок коду за кількістю процесорів у системі!

Примітка: після відкриття algorithmsбібліотеки, і особливо foreach, я пройшов два-три місяці, коли писав сміховинно маленькі структури "помічників", які звели з розуму ваших колег-розробників. Після цього часу я повернувся до прагматичного підходу - маленькі петлеві тіла більше foreachне заслуговують на :)

Обов'язкове посилання на ітераторах - це книга "Extended STL" .

У GoF є невеликий маленький абзац у кінці шаблону Iterator, який говорить про цю марку ітерації; це називається "внутрішнім ітератором". Подивіться і тут .


15

Тому що вона більш орієнтована на об’єкти. якщо ви повторюєте індекс, ви припускаєте:

а) що ці об'єкти впорядковані
б) що ці об'єкти можуть бути отримані індексом
в) що приріст індексу буде досягати кожного елемента
г), що цей індекс починається з нуля

За допомогою ітератора ви говорите: «дай мені все, щоб я могла працювати з ним», не знаючи, що є базовою реалізацією. (У Java є колекції, до яких не можна отримати доступ через індекс)

Крім того, з ітератором не потрібно турбуватися про вихід за межі масиву.


2
Я не думаю, що "об'єктно-орієнтована" - це правильний термін. Ітератори не є об'єктно-орієнтованими в дизайні. Вони просувають функціональне програмування більше, ніж об'єктно-орієнтоване програмування, оскільки вони заохочують відокремлювати алгоритми від класів.
wilhelmtell

Крім того, ітератори не допомагають уникнути виходу за межі. Стандартні алгоритми так, але самі ітератори цього не роблять.
wilhelmtell

Досить справедливо @wilhelmtell, я, очевидно, думаю про це з точки зору Java.
цинічник

1
І я думаю, що це популяризує ОО, оскільки воно відокремлює операції над колекціями від впровадження цієї колекції. Колекція об'єктів не обов'язково повинна знати, які алгоритми слід використовувати для роботи з ними.
цинічник

Насправді є версії STL, які перевірили ітератори, це означає, що це викине якесь виключення поза межами меж, коли ви спробуєте зробити щось із цим ітератором.
Daemin

15

Ще одна приємна річ про ітераторів - це те, що вони краще дозволяють висловити (і застосувати) свої переваги const. Цей приклад гарантує, що ви не будете змінювати вектор посеред вашого циклу:


for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
    // Foo & foo = *pos; // this won't compile
    const Foo & foo = *pos; // this will compile
}

Це виглядає розумно, але я все ще сумніваюся, що якщо це є причиною наявності const_iterator. Якщо я змінюю вектор у циклі, я роблю це з причини, і в 99,9% часу ця зміна не є випадковістю, а для решти, це просто помилка, як і будь-які помилки в коді автора потрібно виправити. Оскільки в Java та багатьох інших мовах взагалі немає об'єкта const, але у користувачів цих мов ніколи не виникає проблем із підтримкою const у цих мовах.
neevek

2
@neevek Якщо це не причина виникнення const_iterator, то що на Землі може бути причиною?
підкреслюй_

@underscore_d, мені теж цікаво. Я в цьому не знаю, просто відповідь не є переконливою для мене.
neevek

15

Крім усіх інших відмінних відповідей ... intможе бути недостатньо великим для вашого вектора. Натомість, якщо ви хочете використовувати індексацію, використовуйте size_typeдля свого контейнера:

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}

1
@Pat Notz, це дуже хороший момент. Під час перенесення програми Windows на базі STL до x64 мені довелося зіткнутися з сотнями попереджень про привласнення size_t до int, що, можливо, спричиняє усічення.
bk1e

1
Не кажучи вже про те , що типи розміру знака і підписаний ИНТА, тому у вас є не-інтуїтивне, перетворення помилки приховування відбувається тільки для порівняння int iз myvector.size().
Адріан Маккарті

12

Напевно, слід зазначити, що ви також можете зателефонувати

std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);


7

Ітератори STL в основному є, щоб алгоритми STL, як сортування, могли бути незалежними від контейнерів.

Якщо ви просто хочете переключити всі записи у векторі, просто використовуйте стиль циклу індексу.

Це менш типізувати та легше розбирати для більшості людей. Було б добре, якби у C ++ був простий цикл передбачень, не переходячи за борт із магією шаблону.

for( size_t i = 0; i < some_vector.size(); ++i )
{
   T& rT = some_vector[i];
   // now do something with rT
}
'

5

Я не думаю, що це має велике значення для вектора. Я вважаю за краще використовувати індекс самостійно, оскільки вважаю це більш читабельним, і ви можете робити випадковий доступ, наприклад, стрибки вперед на 6 елементів або стрибки назад, якщо це необхідно.

Мені також подобається зробити посилання на предмет всередині циклу, як це, так що навколо місця не так багато:

for(size_t i = 0; i < myvector.size(); i++)
{
    MyClass &item = myvector[i];

    // Do stuff to "item".
}

Використання ітератора може бути корисним, якщо ви думаєте, що вам може знадобитися замінити вектор списком в якийсь момент майбутнього, і він також виглядає більш стильно для виродків STL, але я не можу думати про будь-яку іншу причину.


більшість алгоритмів працюють один раз на кожен елемент контейнера послідовно. Звичайно, є винятки, коли ви хочете пройти колекцію у певному порядку чи порядку, але в цьому випадку я б дуже постарався і написати алгоритм, який інтегрується з STL і який працює з ітераторами.
wilhelmtell

Це сприятиме повторному використанню та уникненню пізніх помилок. Тоді я б назвав цей алгоритм так само, як і будь-який інший стандартний алгоритм, з ітераторами.
wilhelmtell

1
Навіть не потрібно заздалегідь (). Ітератор має ті ж оператори + = і - =, що й індекс (для векторних та векторних контейнерів).
MSalters

I prefer to use an index myself as I consider it to be more readableлише в деяких ситуаціях; в інших індекси швидко стають безладними. and you can do random accessщо зовсім не є унікальною особливістю індексів: див. en.cppreference.com/w/cpp/concept/RandomAccessIterator
underscore_d

3

Друга форма відображає те, що ви робите більш точно. У вашому прикладі, вам не байдуже значення i, насправді - все, що вам потрібно, це наступний елемент в ітераторі.


3

Дізнавшись трохи більше про тему цієї відповіді, я розумію, що це було трохи надмірне спрощення. Різниця між цією петлею:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

І ця петля:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

Досить мінімально. Насправді синтаксис виконання циклів таким чином, схоже, на мене зростає:

while (it != end){
    //do stuff
    ++it;
}

Ітератори розблоковують деякі досить потужні декларативні функції, і в поєднанні з бібліотекою алгоритмів STL ви можете зробити досить прикольні речі, які виходять за межі адміністративного індексу масиву.


Правда полягає в тому, що якби всі ітератори були настільки компактними, як ваш остаточний приклад, прямо з коробки, я б з ними мало проблем. Звичайно, це насправді дорівнює for (Iter it = {0}; it != end; ++it) {...}- ви щойно декларували декларацію - тому стислість не сильно відрізняється від вашого другого прикладу. Все-таки +1.
Інженер

3

Індексація вимагає додаткової mulоперації. Наприклад, vector<int> vкомпілятор перетворюється v[i]на &v + sizeof(int) * i.


Напевно, це не є істотним недоліком щодо ітераторів у більшості випадків, але це добре знати.
nobar

3
Можливо, для ізольованих одноелементних доступу. Але якщо ми говоримо про петлі - як це було в ОП - я майже впевнений, що ця відповідь заснована на уявному неоптимізувальному компіляторі. Будь-який напівпристойний матиме достатньо можливостей та ймовірності кешувати sizeofта просто додавати його один раз за ітерацію, а не робити щоразу повторний розрахунок зміщення.
підкреслюй_24

2

Під час ітерації вам не потрібно знати кількість елементів, які потрібно обробити. Вам просто потрібен предмет, і ітератори роблять такі речі дуже добре.


2

Ще ніхто не згадував, що однією з переваг індексів є те, що вони не стають недійсними, коли ви додаєте до суміжного контейнера, як-от std::vector, тому ви можете додавати елементи до контейнера під час ітерації.

Це також можливо за допомогою ітераторів, але ви повинні зателефонувати reserve(), і тому потрібно знати, скільки елементів ви додасте.


1

Вже кілька хороших моментів. У мене є кілька додаткових коментарів:

  1. Якщо припустити, що ми говоримо про стандартну бібліотеку C ++, "вектор" має на увазі контейнер з випадковим доступом, який має гарантії C-масиву (випадковий доступ, компонування пам'яті contiguos тощо). Якби ви сказали "some_container", багато з вищезазначених відповідей були б більш точними (незалежність контейнера тощо).

  2. Щоб усунути будь-які залежності від оптимізації компілятора, ви можете перемістити some_vector.size () з циклу в індексованому коді, наприклад:

    const size_t numElems = some_vector.size ();
    for (size_t i = 0; i 
  3. Завжди попередньо збільшуючи ітератори та ставтесь до інкрементів як до виняткових випадків.

for (some_iterator = some_vector.begin (); some_iterator! = some_vector.end (); ++ some_iterator) {// робити речі}

Отже, припускаючи та індексуючий, std::vector<>як контейнер, немає вагомих причин віддавати перевагу одному перед іншим, послідовно проходячи через контейнер. Якщо вам доводиться часто посилатися на старі чи новіші індекси елемнентів, то індексована версія є більш доречною.

Загалом, використання ітераторів є кращим, оскільки алгоритми використовують їх, а поведінку можна контролювати (і неявно документувати), змінюючи тип ітератора. Місце розташування масиву можна використовувати замість ітераторів, але синтаксична різниця буде дотримуватися.


1

Я не використовую ітераторів з тієї ж причини, що мені не подобаються заяви-проповеди. При наявності декількох внутрішніх циклів досить складно відслідковувати глобальні / членські змінні, не запам'ятовуючи також усіх локальних значень та імен ітераторів. Мені здається корисним використовувати два набори індексів для різних випадків:

for(int i=0;i<anims.size();i++)
  for(int j=0;j<bones.size();j++)
  {
     int animIndex = i;
     int boneIndex = j;


     // in relatively short code I use indices i and j
     ... animation_matrices[i][j] ...

     // in long and complicated code I use indices animIndex and boneIndex
     ... animation_matrices[animIndex][boneIndex] ...


  }

Я навіть не хочу скорочувати такі речі, як "animation_matrices [i]", до якогось випадкового "anim_matrix" -названого-ітератора, тому що тоді ви не можете чітко бачити, з якого масиву походить це значення.


Я не бачу, наскільки показники в цьому сенсі кращі. Ви можете легко використовувати ітератори і просто вибрати конвенцію їх імена: it, jt, ktі т.д. , або навіть просто продовжувати використовувати i, j, kі т.д. І якщо вам потрібно точно знати , що ітератор представляє, то мені що - щось на зразок for (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()б більш описовий ніж постійно індексувати подібне animation_matrices[animIndex][boneIndex].
підкреслюй_

уау, це відчувається як століття тому, коли я писав цю думку. в даний час використовуються як ітератори foreach, так і c ++ без особливих зусиль. Я думаю, що робота з кодом баггі роками формує свою толерантність, тому легше приймати всі синтаксиси та умовності ... доки це працює, і доки ви можете піти додому, ви знаєте;)
AareP

Ха-ха, дійсно, я не дуже дивився на те, скільки років це було раніше! Ще щось, про що я якось не думав минулого разу, було те, що нині ми також маємо forцикл на основі діапазону , що робить ітераторський спосіб зробити це ще більш стислим.
підкреслюй_12

1
  • Якщо вам подобається, що ви знаходитесь поруч із металом / не довіряєте деталям їх реалізації, не використовуйте ітераторів.
  • Якщо ви регулярно вимикаєте один тип колекції на інший під час розробки, використовуйте ітератори.
  • Якщо вам важко згадати, як ітератувати різні колекції (можливо, у вас є кілька типів із кількох різних зовнішніх джерел у використанні), використовуйте ітератори для уніфікації засобів, за допомогою яких ви переходите через елементи. Це стосується, наприклад, переключення зв'язаного списку зі списком масивів.

Дійсно, це все є. Це не так, як якщо б ви в середньому набрали більшої стислості в будь-якому випадку, і якщо стислість справді ваша мета, ви завжди можете повернутися до макросів.


1

Якщо у вас є доступ до функцій C ++ 11 , ви також можете використовувати цикл на основі діапазонуfor для ітерації над вашим вектором (або будь-яким іншим контейнером) наступним чином:

for (auto &item : some_vector)
{
     //do stuff
}

Перевага цього циклу полягає в тому, що ви можете отримати доступ до елементів вектора безпосередньо через itemзмінну, не ризикуючи зіпсувати індекс або зробити помилку при перенаправлення ітератора. Крім того, заповнювач заповнення autoзаважає вам повторювати тип елементів контейнера, що ще більше наближує вас до незалежного від контейнера рішення.

Примітки:

  • Якщо вам потрібен індекс елементів у вашому циклі та operator[]існує для вашого контейнера (і це досить швидко для вас), то краще перейдіть до першого шляху.
  • forЦикл на основі діапазону не можна використовувати для додавання / видалення елементів у контейнер / з. Якщо ви хочете це зробити, то краще дотримуйтесь рішення, яке дав Брайан Меттьюз.
  • Якщо ви не хочете , щоб змінити елементи в контейнері, то ви повинні використовувати ключове слово constнаступним чином : for (auto const &item : some_vector) { ... }.

0

Навіть краще, ніж "казати процесору, що робити" (обов'язково) - це "розповісти бібліотекам, що ти хочеш" (функціонально).

Тож замість використання циклів слід вивчити алгоритми, присутні у stl.



0

Я завжди використовую індекс масиву, тому що для багатьох моїх програм потрібно щось на зразок "відображення мініатюрного зображення". Тому я написав щось подібне:

some_vector[0].left=0;
some_vector[0].top =0;<br>

for (int i = 1; i < some_vector.size(); i++)
{

    some_vector[i].left = some_vector[i-1].width +  some_vector[i-1].left;
    if(i % 6 ==0)
    {
        some_vector[i].top = some_vector[i].top.height + some_vector[i].top;
        some_vector[i].left = 0;
    }

}

0

Обидві реалізації правильні, але я вважаю за краще цикл 'для'. Оскільки ми вирішили використовувати вектор, а не будь-який інший контейнер, використання індексів було б найкращим варіантом. Використання ітераторів з векторами втратить саму вигоду від об'єктів у постійних блоках пам'яті, що полегшить їх доступ.


2
"Використання ітераторів з векторами втратить саму вигоду від об'єктів у постійних блоках пам'яті, що полегшить їхній доступ." [цитування потрібне]. Чому? Як ви вважаєте, приріст ітератора до суміжного контейнера не може бути реалізований як просте доповнення?
підкреслюй_24

0

Я відчув, що жодна з відповідей тут не пояснює, чому мені подобаються ітератори як загальна концепція щодо індексації в контейнери. Зауважте, що більша частина мого досвіду використання ітераторів насправді не походить із C ++, а з мов програмування вищого рівня, таких як Python.

Інтерфейс ітератора пред'являє менші вимоги до споживачів вашої функції, що дозволяє споживачам робити більше з нею.

Якщо все, що вам потрібно, - це можливість переадресувати ітерацію, розробник не обмежується використанням контейнерів, що індексуються - вони можуть використовувати будь-який клас, що реалізує operator++(T&), operator*(T)та operator!=(const &T, const &T).

#include <iostream>
template <class InputIterator>
void printAll(InputIterator& begin, InputIterator& end)
{
    for (auto current = begin; current != end; ++current) {
        std::cout << *current << "\n";
    }
}

// elsewhere...

printAll(myVector.begin(), myVector.end());

Ваш алгоритм працює в тому випадку, коли вам це потрібно - ітерація над вектором - але він також може бути корисним для додатків, яких ви не обов'язково передбачаєте:

#include <random>

class RandomIterator
{
private:
    std::mt19937 random;
    std::uint_fast32_t current;
    std::uint_fast32_t floor;
    std::uint_fast32_t ceil;

public:
    RandomIterator(
        std::uint_fast32_t floor = 0,
        std::uint_fast32_t ceil = UINT_FAST32_MAX,
        std::uint_fast32_t seed = std::mt19937::default_seed
    ) :
        floor(floor),
        ceil(ceil)
    {
        random.seed(seed);
        ++(*this);
    }

    RandomIterator& operator++()
    {
        current = floor + (random() % (ceil - floor));
    }

    std::uint_fast32_t operator*() const
    {
        return current;
    }

    bool operator!=(const RandomIterator &that) const
    {
        return current != that.current;
    }
};

int main()
{
    // roll a 1d6 until we get a 6 and print the results
    RandomIterator firstRandom(1, 7, std::random_device()());
    RandomIterator secondRandom(6, 7);
    printAll(firstRandom, secondRandom);

    return 0;
}

Спроба впровадити оператор з квадратними дужками, який робить щось подібне до цього ітератора, була б надуманою, тоді як реалізація ітератора відносно проста. Оператор з квадратними дужками також впливає на можливості вашого класу - які ви можете проіндексувати до будь-якої довільної точки - що може бути важко або неефективно реалізувати.

Ітератори також піддаються декору . Люди можуть записувати ітератори, які беруть ітератор у свій конструктор і розширюють його функціональність:

template<class InputIterator, typename T>
class FilterIterator
{
private:
    InputIterator internalIterator;

public:
    FilterIterator(const InputIterator &iterator):
        internalIterator(iterator)
    {
    }

    virtual bool condition(T) = 0;

    FilterIterator<InputIterator, T>& operator++()
    {
        do {
            ++(internalIterator);
        } while (!condition(*internalIterator));

        return *this;
    }

    T operator*()
    {
        // Needed for the first result
        if (!condition(*internalIterator))
            ++(*this);
        return *internalIterator;
    }

    virtual bool operator!=(const FilterIterator& that) const
    {
        return internalIterator != that.internalIterator;
    }
};

template <class InputIterator>
class EvenIterator : public FilterIterator<InputIterator, std::uint_fast32_t>
{
public:
    EvenIterator(const InputIterator &internalIterator) :
        FilterIterator<InputIterator, std::uint_fast32_t>(internalIterator)
    {
    }

    bool condition(std::uint_fast32_t n)
    {
        return !(n % 2);
    }
};


int main()
{
    // Rolls a d20 until a 20 is rolled and discards odd rolls
    EvenIterator<RandomIterator> firstRandom(RandomIterator(1, 21, std::random_device()()));
    EvenIterator<RandomIterator> secondRandom(RandomIterator(20, 21));
    printAll(firstRandom, secondRandom);

    return 0;
}

Хоча ці іграшки можуть здатися мирськими, не важко уявити, як використовувати ітератори та декоратори ітераторів, щоб робити потужні речі з простим інтерфейсом - прикрашати ітератор результатів бази даних, призначений лише для переходу, ітератором, який конструює об'єкт моделі з одного результату, наприклад . Ці шаблони дають можливість ітерації нескінченних наборів для ефективної пам’яті та, маючи фільтр, подібний до того, про який я писав вище, потенційно ледачу оцінку результатів.

Частина потужності шаблонів C ++ - це ваш інтерфейс ітератора, коли його застосовують до подібних масивів C фіксованої довжини , що перетворюються на просту та ефективну арифметику покажчика , що робить його справді абстракцією від нуля.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.