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

StackOverflow https://stackoverflow.com/questions/409348

  •  03-07-2019
  •  | 
  •  

Вопрос

Каков правильный способ перебора вектора в C++?

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

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.

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

Это было полезно?

Решение

Итерация в обратном направлении

Видеть этот ответ.

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

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

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

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

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

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

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

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

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

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

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

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

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

Использование диапазона C++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 Однако подход может уступить.

Другие советы

Прошло четыре года, Google дал мне этот ответ.С стандартный С++11 (он же С++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++ -std=c++0x -o auto auto.cpp

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

$ ./auto
17
12
23
42

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

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

Что касается ответа Йоханнеса Шауба:

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) {
...

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

Также обычно итерация по контейнеру в C++ выполняется с помощью итераторы, так.

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 и так далее).

Использовать size_t :

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

Цитирование Википедия:

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

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

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

#include <boost/foreach.hpp>

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

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

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

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

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

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

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

В завершение синтаксис C++11 допускает еще одну версию итераторов (ссылка):

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
}

В С++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;
}
for (vector<int>::iterator it = polygon.begin(); it != polygon.end(); it++)
    sum += *it; 

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

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

А <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);

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

Непонятная, но важная деталь:если вы скажете «for(auto it)» следующим образом, вы получите копию объекта, а не сам элемент:

struct Xs{int i} x;
x.i = 0;
vector <Xs> v;
v.push_back(x);
for(auto it : v)
    it.i = 1;         // doesn't change the element v[0]

Чтобы изменить элементы вектора, вам необходимо определить итератор как ссылку:

for(auto &it : v)

Если ваш компилятор поддерживает это, вы можете использовать диапазон, основанный на for, для доступа к векторным элементам:

vector<float> vertices{ 1.0, 2.0, 3.0 };

for(float vertex: vertices){
    std::cout << vertex << " ";
}

Распечатки:1 2 3 .Обратите внимание: вы не можете использовать этот метод для изменения элементов вектора.

Два сегмента кода работают одинаково.Однако маршрут unsigned int» верен.Использование беззнаковых типов int будет лучше работать с вектором в том экземпляре, в котором вы его использовали.Вызов функции-члена size() для вектора возвращает целочисленное значение без знака, поэтому вы хотите сравнить переменную «i» со значением ее собственного типа.

Кроме того, если вас все еще немного беспокоит то, как «unsigned int» выглядит в вашем коде, попробуйте «uint».По сути, это сокращенная версия unsigned int, и она работает точно так же.Вам также не нужно включать другие заголовки, чтобы использовать его.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top