Итерация по std :: vector: unsigned vs знаковая переменная со знаком

374

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

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

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 могут быть опасны, если они не используются правильно, поэтому - это правильно?

  • 7
    Неподписанный является правильным, потому что polygon.size () имеет тип unsigned. Без знака означает положительный всегда или 0. Это все, что это значит. Таким образом, если использование переменной всегда только для счетчиков, тогда беззнаковый является правильным выбором.
  • 0
    Чтобы устранить предупреждение о подписанном / неподписанном, просто замените int на uint ( unsigned int ) в объявлении i .
Показать ещё 7 комментариев
Теги:
signed
stl
unsigned

13 ответов

624
Лучший ответ

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

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

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

Это почти идентично. Просто измените итераторы/декремент свопинга с помощью приращения. Вы должны предпочесть итераторы. Некоторые люди говорят вам использовать 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.

  • 0
    Размер указателей типа: использование diff_type может быть более переносимым. попробуйте iterator_traits <element_type *> :: diff_type. это один глоток декларации, но он более переносимый ...
  • 0
    wilhelmtell, для чего я должен использовать diff_type? sizeof определен для возврата size_t :) я вас не понимаю. если бы я вычел указатели друг от друга, разность-тип был бы правильным выбором.
Показать ещё 9 комментариев
132

Прошло четыре года, 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, программа должна быть независимой от платформы (и компилятора).

  • 6
    C ++ 11 дает вам for (auto& val: vec)
  • 0
    @flexo Спасибо, я не знаю, как я мог забыть это. Я полагаю, не хватает C ++. Не могу поверить, что есть что-то практичное (на самом деле это был синтаксис JavaScript). Я изменил ответ, чтобы включить это.
Показать ещё 6 комментариев
43

В конкретном случае в вашем примере я бы использовал алгоритмы 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 );
38

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

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) {
...
  • 1
    Следует пояснить, что это применимо только тогда, когда T является аргументом шаблона, и, таким образом, выражение std::vector<T*>::iterator является зависимым именем. Для того чтобы зависимое имя было проанализировано как тип, оно должно предшествовать ключевому слову typename , как указывает диагностика.
15

Вызов 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).

  • 0
    Итераторы, как правило, хорошая идея, хотя я сомневаюсь, что необходимо хранить «конец» в отдельной переменной, и все это можно сделать внутри оператора for (;;).
  • 1
    Я знаю, что begin () и end () амортизируются постоянным временем, но обычно я нахожу это более читабельным, чем объединять все в одну строку.
Показать ещё 6 комментариев
10

Используйте 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-разрядные архитектуры становятся более распространенными.

  • 0
    size_t ОК для вектора, так как он должен хранить все объекты в массиве (тоже сам объект), но std :: list может содержать больше, чем элементы size_t!
  • 1
    size_t обычно достаточно для перечисления всех байтов в адресном пространстве процесса. Хотя я понимаю, что на некоторых экзотических архитектурах это может быть не так, я бы предпочел не беспокоиться об этом.
Показать ещё 1 комментарий
6

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

#include <boost/foreach.hpp>

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

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

  • 2
    Хороший ответ на какой-то другой вопрос (как мне перебрать вектор?), Но совсем не то, о чем спрашивал OP (в чем смысл предупреждения о неподписанной переменной?)
  • 3
    Ну, он спросил, каков правильный способ итерации по вектору. Так что кажется достаточно актуальным. Предупреждение состоит лишь в том, почему он не доволен своим текущим решением.
5

В С++ 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;
}
5

Чтобы быть полным, синтаксис С++ 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
}
4

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

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

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

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

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

4
for (vector<int>::iterator it = polygon.begin(); it != polygon.end(); it++)
    sum += *it; 
  • 2
    Для вектора это хорошо, но обычно лучше использовать ++ это, а не ++, если сам итератор нетривиален.
  • 0
    Лично я привык к использованию ++ i, но я думаю, что большинство людей предпочитают стиль i ++ (фрагмент кода VS по умолчанию для «for» - это i ++). Просто мысль
Показать ещё 1 комментарий
2

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

  • 2
    Я думаю, что это ужасный кандидат, который следует игнорировать - его легко исправить, и время от времени возникают подлинные ошибки из-за ошибок, сравнивающих значения со знаком и без знака ненадлежащим образом. Например, в этом случае, если размер больше, чем INT_MAX, цикл никогда не завершается.
  • 0
    ... или, может быть, это немедленно прекращается. Один из двух. Зависит от того, преобразуется ли подписанное значение в неподписанное для сравнения или без знака преобразуется в подписанное. На 64-битной платформе с 32-битным int, хотя, как и в win64, int будет повышен до size_t, и цикл никогда не закончится.
Показать ещё 2 комментария
1

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

Стандартный заголовок <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);

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню