Итерация по вектору C ++ с использованием цикла for

140

Я новичок в языке C ++. Я начал использовать векторы и заметил, что во всем коде, который я вижу, для итерации по вектору через индексы, первый параметр forцикла всегда основан на векторе. В Java я мог бы сделать что-то вроде этого с ArrayList:

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

Есть ли причина, по которой я не вижу этого в C ++? Это плохая практика?

Flynn
источник
1
Цикл for не является функцией, поэтому он не имеет параметров (или аргументов, что вы и передаете). Ты имеешь в виду что-то подобное std::vector<int>::size_type i = 0;, или, может быть std::vector<int>::iterator it = vector.begin();?
Крис
Именно все примеры, которые я вижу, написаны так.
Флинн
4
В Java я бы предпочел цикл для каждого или использовать итераторы. Практически так же, как C ++, хотя и немного другой синтаксис.
Джесси Гуд
10
Большинство ответов здесь неверно предполагают, что вопрос будет следующим: Каков наилучший / самый короткий способ перебора std::vector? фактический вопрос, который здесь задают, таков : есть ли причина, по которой я не вижу этого в C ++? Это плохая практика? ака Почему я всегда вижу код в C ++, который использует итераторы при повторении std::vector?
Alok Save

Ответы:

93

Есть ли причина, по которой я не вижу этого в C ++? Это плохая практика?

Нет. Это неплохая практика, но следующий подход придает вашему коду определенную гибкость .

Обычно в пре-C ++ 11 коде для перебора элементов контейнера используются итераторы, что-то вроде:

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

Это потому, что это делает код более гибким.

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

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

Alok Save
источник
25
Может ли кто-нибудь объяснить мне, почему в данном конкретном случае / фрагменте кода вы советуете итераторам по индексации? О какой «гибкости» вы говорите? Лично мне не нравятся итераторы, они раздувают код - просто больше символов, чтобы напечатать для того же самого эффекта. Особенно, если ты не можешь использовать auto.
Фиолетовый Жираф
8
@VioletGiraffe: при использовании итераторов трудно ошибиться в некоторых случаях, таких как пустые диапазоны, а код более подробный. Конечно, это вопрос или восприятие и выбор, поэтому его можно обсуждать бесконечно.
Alok Save
9
Почему вы показываете только, как объявить итератор, а не как использовать его для цикла ...?
underscore_d
117

Причина, по которой вы не видите такой практики, весьма субъективна и не может дать однозначного ответа, потому что я видел много кода, который использует упомянутый вами способ, а не 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;
iammilind
источник
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функций определены, даже для не относящихся к классу типов, таких как статические массивы. Смотрите здесь: Как работает диапазон на основе для простых массивов?

Johnb
источник
5
auto, free начало / конец также 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();
}
DiGMi
источник
не std::vector<T>::size_typeвсегда size_t? Это тип, который я всегда использую для этого.
Фиолетовый Жираф
1
@VioletGiraffe Я уверен, что вы правы (на самом деле не проверял), но лучше использовать std :: vector <T> :: size_type.
DiGMi
8

Есть несколько веских причин использовать итераторы, некоторые из которых упомянуты здесь:

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

то есть, если вы переходите от std :: vector к std :: list или std :: set, вы не можете использовать числовые индексы, чтобы получить значение, которое вы содержите. Использование итератора все еще действует.

Время отлова неверной итерации

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

Эдди Паркер
источник
1
не могли бы вы указать на какую-то статью / пост, который мы объясняем вышеизложенным примером кода? было бы замечательно! или если бы вы могли добавить один :)
Anu
5

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

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

Диомидис Спинеллис
источник
4

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

навсегда
источник
3

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

Итерация вектора с использованием auto и for loop

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

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

Вывод:

1 2 3 4 5

Вы также можете использовать этот метод для итерации наборов и списков. Использование auto автоматически определяет тип данных, используемый в шаблоне, и позволяет использовать его. Таким образом, даже если мы имели vectorиз stringили charтот же синтаксис будет работать нормально

Hrishikesh
источник
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;
башар
источник