Итерация над std::vector: неподписанная и подписанная индексная переменная

Каков правильный способ итерации над вектором в С++?

Рассмотрим эти два фрагмента кода, это прекрасно работает:

for (unsigned i=0; i < polygon.size(); i++) {
    sum += polygon[i];
}

и этот:

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

который генерирует warning: comparison between signed and unsigned integer expressions.

Я новичок в мире С++, поэтому переменная unsigned выглядит немного пугающей для меня, и я знаю, что переменные unsigned могут быть опасны, если они не используются правильно, поэтому - это правильно?

Ответ 1

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

См. этот ответ.

Итерация вперед

Это почти идентично. Просто измените итераторы/декремент свопинга с помощью приращения. Вы должны предпочесть итераторы. Некоторые люди говорят вам использовать std::size_t как тип индексной переменной. Однако это не переносимо. Всегда используйте size_type typedef контейнера (хотя вы можете уйти только с преобразованием в случае повторного итерации, он может фактически ошибиться полностью в случае повторного итерации при использовании std::size_t, в случае std::size_t шире, чем typedef size_type):

Использование std::vector

Использование итераторов

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

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

Использование диапазона С++ 11

for(auto const& value: a) {
     /* std::cout << value; ... */

Использование индексов

for(std::vector<int>::size_type i = 0; i != v.size(); i++) {
    /* std::cout << someVector[i]; ... */
}

Использование массивов

Использование итераторов

for(element_type* it = a; it != (a + (sizeof a / sizeof *a)); it++) {
    /* std::cout << *it; ... */
}

Использование диапазона С++ 11

for(auto const& value: a) {
     /* std::cout << value; ... */

Использование индексов

for(std::size_t i = 0; i != (sizeof a / sizeof *a); i++) {
    /* std::cout << a[i]; ... */
}

Читайте в обратном итерационном ответе, какую проблему может подойти подход sizeof.

Ответ 2

Прошло четыре года, Google дал мне этот ответ. С стандартным С++ 11 (aka С++ 0x) на самом деле есть новый приятный способ сделать это (ценой разрыва обратной совместимости): новый auto. Это избавляет вас от необходимости явно указывать тип используемого итератора (повторяя снова векторный тип), когда это очевидно (для компилятора), какой тип использовать. Если v является вашим vector, вы можете сделать что-то вроде этого:

for ( auto i = v.begin(); i != v.end(); i++ ) {
    std::cout << *i << std::endl;
}

С++ 11 идет еще дальше и дает вам специальный синтаксис для итерации над коллекциями, такими как векторы. Это устраняет необходимость записи вещей, которые всегда одинаковы:

for ( auto &i : v ) {
    std::cout << i << std::endl;
}

Чтобы увидеть его в рабочей программе, создайте файл auto.cpp:

#include <vector>
#include <iostream>

int main(void) {
    std::vector<int> v = std::vector<int>();
    v.push_back(17);
    v.push_back(12);
    v.push_back(23);
    v.push_back(42);
    for ( auto &i : v ) {
        std::cout << i << std::endl;
    }
    return 0;
}

Как и в случае с этим, когда вы компилируете это с помощью g++, вам обычно нужно установить его для работы с новым стандартом, добавив дополнительный флаг:

g++ -std=c++0x -o auto auto.cpp

Теперь вы можете запустить пример:

$ ./auto
17
12
23
42

Обратите внимание, что инструкции по компиляции и запуску специфичны для компилятора gnu С++ в Linux, программа должна быть независимой от платформы (и компилятора).

Ответ 3

В конкретном случае в вашем примере я бы использовал алгоритмы STL для этого.

#include <numeric> 

sum = std::accumulate( polygon.begin(), polygon.end(), 0 );

Для более общего, но все же довольно простого случая, я бы пошел с:

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>

using namespace boost::lambda;
std::for_each( polygon.begin(), polygon.end(), sum += _1 );

Ответ 4

Относительно ответа Йоханнеса Шауба:

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

Это может работать с некоторыми компиляторами, но не с gcc. Проблема здесь в том, что std::vector:: iterator - это тип, переменная (член) или функция (метод). Мы получаем следующую ошибку с gcc:

In member function ‘void MyClass<T>::myMethod()’:
error: expected `;' before ‘it’
error: ‘it’ was not declared in this scope
In member function ‘void MyClass<T>::sort() [with T = MyClass]’:
instantiated from ‘void MyClass<T>::run() [with T = MyClass]’
instantiated from here
dependent-name ‘std::vector<T*,std::allocator<T*> >::iterator’ is parsed as a non-type, but instantiation yields a type
note: say ‘typename std::vector<T*,std::allocator<T*> >::iterator’ if a type is meant

В решении используется ключевое слово 'typename', как сказано:

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

Ответ 5

Вызов vector<T>::size() возвращает значение типа std::vector<T>::size_type, а не int, unsigned int или иначе.

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

std::vector<T>::iterator i = polygon.begin();
std::vector<T>::iterator end = polygon.end();

for(; i != end; i++){
    sum += *i;
}

Где T - тип данных, которые вы храните в векторе.

Или используя различные итерационные алгоритмы (std::transform, std::copy, std::fill, std::for_each et cetera).

Ответ 6

Используйте size_t:

for (size_t i=0; i < polygon.size(); i++)

Цитирование Wikipedia:

Заголовочные файлы stdlib.h и stddef.h определяют тип данных под названием size_t, который используется для представления размера объекта. Функции библиотеки, которые принимают размеры, предполагают, что они имеют тип size_t, а оператор sizeof принимает значение size_t.

Фактический тип size_t зависит от платформы; распространенная ошибка состоит в том, чтобы предположить, что size_t - это то же самое, что и unsigned int, что может привести к ошибкам программирования, особенно в том случае, когда 64-разрядные архитектуры становятся более распространенными.

Ответ 7

Обычно я использую BOOST_FOREACH:

#include <boost/foreach.hpp>

BOOST_FOREACH( vector_type::value_type& value, v ) {
    // do something with 'value'
}

Он работает с контейнерами STL, массивами, строками в стиле C и т.д.

Ответ 8

Чтобы быть полным, синтаксис С++ 11 включает только одну версию для итераторов ( ref):

for(auto it=std::begin(polygon); it!=std::end(polygon); ++it) {
  // do something with *it
}

Что также удобно для обратной итерации

for(auto it=std::end(polygon)-1; it!=std::begin(polygon)-1; --it) {
  // do something with *it
}

Ответ 9

В С++ 11

Я бы использовал общие алгоритмы типа for_each, чтобы избежать поиска правильного типа итератора и лямбда-выражения, чтобы избежать дополнительных именованных функций/объектов.

Короткий "симпатичный" пример для вашего конкретного случая (предполагая, что полигон представляет собой вектор целых чисел):

for_each(polygon.begin(), polygon.end(), [&sum](int i){ sum += i; });

проверено на: http://ideone.com/i6Ethd

Не забывайте включить: алгоритм и, конечно же, вектор:)

На самом деле у Microsoft есть хороший пример: Источник: http://msdn.microsoft.com/en-us/library/dd293608.aspx

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

int main() 
{
   // Create a vector object that contains 10 elements.
   vector<int> v;
   for (int i = 1; i < 10; ++i) {
      v.push_back(i);
   }

   // Count the number of even numbers in the vector by 
   // using the for_each function and a lambda.
   int evenCount = 0;
   for_each(v.begin(), v.end(), [&evenCount] (int n) {
      cout << n;
      if (n % 2 == 0) {
         cout << " is even " << endl;
         ++evenCount;
      } else {
         cout << " is odd " << endl;
      }
   });

   // Print the count of even numbers to the console.
   cout << "There are " << evenCount 
        << " even numbers in the vector." << endl;
}

Ответ 10

for (vector<int>::iterator it = polygon.begin(); it != polygon.end(); it++)
    sum += *it; 

Ответ 11

Немного истории:

Чтобы представить, является ли число отрицательным или нет, компьютер использует бит "знака". int - это тип подписанных данных, означающий, что он может содержать положительные и отрицательные значения (от -2 миллиардов до 2 миллиардов). Unsigned может хранить только положительные числа (и поскольку он не тратит немного времени на метаданные, он может хранить больше: от 0 до 4 миллиардов).

std::vector::size() возвращает Unsigned, как может вектор иметь отрицательную длину?

Предупреждение сообщает вам, что правый операнд вашего оператора неравенства может содержать больше данных, а затем левый.

По существу, если у вас есть вектор с более чем 2 миллиардами записей, и вы используете целое число для индексации, вы столкнетесь с проблемами переполнения (int вернет обратно к отрицательному 2 миллиарду).

Ответ 12

Первый тип правильный и правильный в некотором строгом смысле. (Если вы думаете об этом, размер никогда не может быть меньше нуля.) Это предупреждение поражает меня как одного из хороших кандидатов для игнорирования.

Ответ 13

Рассмотрим, нужно ли вообще выполнять итерацию

Стандартный заголовок <algorithm> предоставляет нам следующие возможности:

using std::begin;  // allows argument-dependent lookup even
using std::end;    // if the container type is unknown here
auto sum = std::accumulate(begin(polygon), end(polygon), 0);

Другие функции библиотеки алгоритмов выполняют общие задачи - убедитесь, что вы знаете, что доступно, если вы хотите сэкономить усилия.