Ітерація через вектор C ++, використовуючи цикл 'для'


140

Я новачок у мові C ++. Я почав використовувати вектори, і помітив, що у всьому коді, який я бачу, щоб повторюватись хоч вектором через індекси, перший параметр forциклу - це завжди щось на основі вектора. У Java я можу зробити щось подібне з ArrayList:

for(int i=0; i < vector.size(); i++){
   vector[i].doSomething();
}

Чи є причина, що я не бачу цього в C ++? Це погана практика?


1
Цикл for не є функцією, тому в ньому немає параметрів (або аргументів, які ви передаєте). Ти std::vector<int>::size_type i = 0;хоч маєш на увазі щось на кшталт , чи, можливо std::vector<int>::iterator it = vector.begin();,?
chris

Точно всі приклади, які я бачу, написані так.
Флінн

4
У Java я вважаю за краще цикл для кожного або використовувати ітератори. Приблизно такий же, як і C ++, хоча дещо інший синтаксис.
Джессі Гарний


10
Більшість відповідей тут неправильно припускають, що питання Q: Який найкращий / найкоротший спосіб ітерації std::vector? , насправді Q запитують тут: Чи є якась причина, що я не бачу цього в C ++? Це погана практика? aka Чому я завжди бачу код у C ++, який використовує ітератори під час повторення std::vector?
Алок Зберегти

Відповіді:


92

Чи є якась причина, що я не бачу цього в C ++? Це погана практика?

Ні. Це не погана практика, але наступний підхід надає вашому коду певну гнучкість .

Зазвичай в коді pre-C ++ 11 для повторення елементів контейнера використовуються ітератори, наприклад:

std::vector<int>::iterator it = vector.begin();

Це тому, що робить код більш гнучким.

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

Примітка: Написання коду, який працює з усіма можливими стандартними контейнерами бібліотеки, не так просто, як може здатися.


25
Чи не може хто-небудь пояснити мені, чому саме в цьому конкретному випадку / фрагменті коду ви консультуєте ітераторів щодо індексації? Про що це "гнучкість", про яку ви говорите? Особисто мені не подобаються ітератори, вони розмивають код - просто більше символів набрати для того ж ефекту. Особливо, якщо ви не можете використовувати auto.
Фіолетова жирафа

8
@VioletGiraffe: Під час використання ітераторів важко помилитися з певними випадками, такими як порожні діапазони, а код є більш багатослівним. Звичайно, це питання або сприйняття та вибір, тому про нього можна дискутувати нескінченно.
Алок Зберегти

9
Чому ви показуєте лише, як оголосити ітератор, а не як використовувати його для виконання циклу ...?
підкреслюй_d

116

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

Нижче можуть бути причини, що люди не розглядають vector.size()спосіб циклічення:

  1. Будучи параноїком щодо виклику size()кожного разу в режимі циклу. Однак або це не випуск, або його можна поправити
  2. Віддаючи перевагу std::for_each()над forсамою петлею
  3. Пізніша зміна контейнера std::vectorна інший (наприклад map, list) також вимагатиме зміни механізму циклічного циклу, оскільки не кожен size()стиль підтримує циклічний цикл

C ++ 11 забезпечує хороший засіб для переміщення через контейнери. Це називається "діапазон на основі циклу" (або "розширений для циклу" на Java).

З невеликим кодом ви можете пройти повне (обов’язкове!) std::vector:

vector<int> vi;
...
for(int i : vi) 
  cout << "i = " << i << endl;

12
Зазначимо лише невеликий недолік діапазону, заснованого на циклі : ви не можете використовувати його #pragma omp parallel for.
liborm

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

87

Найчистіший спосіб ітерації через вектор - через ітератори:

for (auto it = begin (vector); it != end (vector); ++it) {
    it->doSomething ();
}

або (рівнозначно вище)

for (auto & element : vector) {
    element.doSomething ();
}

Перед C ++ 0x, ви повинні замінити auto за типом ітератора та використовувати членські функції замість того, щоб глобальні функції починалися та закінчувалися.

Це, мабуть, те, що ви бачили. У порівнянні з підходом, який ви згадуєте, перевага полягає в тому, що ви не сильно залежите від типу vector. Якщо ви перейдете vectorна інший клас "тип колекції", ваш код, ймовірно, все ще буде працювати. Однак ви можете зробити щось подібне і на Java. Концептуально немає різниці; C ++, однак, використовує шаблони для здійснення цього (порівняно з генеріками на Java); отже, підхід буде працювати для всіх типів, для яких beginі endвизначені функції, навіть для некласових типів, таких як статичні масиви. Дивіться тут: Як працює діапазон роботи для звичайних масивів?


5
Авто, безкоштовний початок / кінець - також C ++ 11. І теж ви повинні використовувати ++ це, а не ++ у багатьох випадках.
ForEveR

Так, ви праві. Реалізація beginі end, однак, є однолінійною.
JohnB

@JohnB - це більше, ніж один-вкладиш, оскільки він працює і для масивів фіксованого розміру. autoз іншого боку, це було б досить складно.
juanchopanza

Якщо він вам потрібен лише для вектора, це однолінійний.
JohnB

І все-таки перший приклад вводить в оману, оскільки він не може працювати в C ++ 03, тоді як ваше фразування говорить про це.
juanchopanza

35

Правильний спосіб зробити це:

for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
    it->doSomething();
 }

Де T - тип класу всередині вектора. Наприклад, якщо клас був CActivity, просто запишіть CActivity замість T.

Цей тип методу буде працювати на всіх STL (Не тільки векторах, що трохи краще).

Якщо ви все ще хочете використовувати індекси, спосіб такий:

for(std::vector<T>::size_type i = 0; i != v.size(); i++) {
    v[i].doSomething();
}

не std::vector<T>::size_typeзавжди size_t? Це тип, який я завжди використовую для цього.
Фіолетова жирафа

1
@VioletGiraffe Я впевнений, що ти маєш рацію (не дуже перевіряв), але краще використовувати std :: vector <T> :: size_type.
DiGMi

8

Є кілька вагомих причин використовувати ітератори, деякі з яких згадуються тут:

Пізніше переміщення контейнерів не скасовує ваш код.

тобто, якщо ви переходите з std :: vector до списку std :: або std :: set, ви не можете використовувати числові індекси, щоб отримати значення, що міститься. Використання ітератора все ще діє.

Виконання недійсної ітерації під час виконання

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


1
Ви можете вказати на якусь статтю / пост, що пояснює вищезазначені моменти з прикладом коду? було б здорово! або якщо ви можете додати один :)
Ану

5

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

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


4

За допомогою STL програмісти використовують iteratorsдля переходу через контейнери, оскільки ітератор - це абстрактне поняття, реалізоване у всіх стандартних контейнерах. Наприклад, std::listвзагалі немає operator [].


3

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

Ітераційний вектор за допомогою автоматичного та циклу

vector<int> vec = {1,2,3,4,5}

for(auto itr : vec)
    cout << itr << " ";

Вихід:

1 2 3 4 5

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


1

Правильний спосіб ітерації циклу та друку його значень такий:

#include<vector>

//declare the vector of type int
vector<int> v;

//insert the 5 element in the vector
for ( unsigned int i = 0; i < 5; i++){
    v.push_back(i);
}

//print those element
for (auto it = 0; it < v.end(); i++){
    std::cout << *it << std::endl;
}

1

Ось більш простий спосіб ітерації та друку значень у векторі.

for(int x: A) // for integer x in vector A
    cout<< x <<" "; 

0
 //different declaration type
    vector<int>v;  
    vector<int>v2(5,30); //size is 5 and fill up with 30
    vector<int>v3={10,20,30};
    
    //From C++11 and onwards
    for(auto itr:v2)
        cout<<"\n"<<itr;
     
     //(pre c++11)   
    for(auto itr=v3.begin(); itr !=v3.end(); itr++)
        cout<<"\n"<<*itr;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.