Значения со знаком и без знака для подсчета в цикле

Итак, у меня в программе есть обычный цикл for для вектора объектов (объекты определенного мной типа, если это уместно):

for(int k = 0; k < objects.size(); k++){ ... }

…и когда я компилирую, я получаю это предупреждение:

warning: comparison between signed and unsigned integer expressions

Это имеет смысл, так как я думаю, size() для вектора возвращает size_t, Но почему это имеет значение? Не является ли определенное количество элементов (или даже фрагментов памяти) целым числом, которое вы можете посчитать? Что еще более важно, поскольку моя программа имеет несколько таких циклов и часто случается с segfault, может ли это быть частью этого?

14

Решение

Проблема возникает когда object.size() возвращает значение, которое больше, чем максимальное представимое значение k. поскольку k подписан, он имеет только половину максимального значения по сравнению с size_t1.

Теперь этого может не произойти в вашем конкретном приложении (в типичной 32-битной системе, в вашей коллекции будет более двух миллиардов объектов), но всегда полезно использовать правильные типы.

1. Упреждающее опровержение: да, это верно только для машин, использующих типичную арифметику с двумя дополнительными компонентами, и для машин, где int а также size_t представлены с использованием одинакового количества битов.

11

Другие решения

Хорошо ответили, уже, но я добавлю свой S / 0.02: «правильный» способ сделать это:

for (typename std::vector<MyObject>::size_type i = 0; i < object.size(); ++i) { ... }

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

С C ++ 11 вы можете воспользоваться decltype:

for (decltype(object.size()) i = 0; i < object.size(); ++i) { ... }

Или вы можете воспользоваться auto:

for (auto i = object.size() - object.size(); i < object.size(); ++i) { ... }

Или вы можете просто использовать size_t, но у вас все еще могут быть сомнения по поводу переполнения, так как vector<MyObject>Тип size_type может быть больше, чем size_t. (Это не так, но нет никаких гарантий):

for (size_t i = 0; i < object.size(); ++i) { ... }

Так что же делать честному программисту?

Абсолютно простое решение — это то, что STL продвигает с самого начала. Кроме того, в начале было также трудно написать:

for (typename std::vector<MyObject>::iterator_type it = object.begin(); it != object.end(); ++it) { ... }

Теперь C ++ 11 действительно помогает вам. У вас есть несколько очень хороших альтернатив, начиная с простых:

for (auto it = object.begin(); it != object.end(); ++it) { ... }

Но это становится еще лучше (барабанная дробь, пожалуйста) …

for (auto& val : object) { ... }

И это тот, который я бы использовал.


Отредактировано, чтобы добавить:

Кори Нельсон в комментарии отмечает, что также возможно кэшировать результат object.end () с помощью:

for (auto it = object.begin(), end = object.end(); it != end; ++it) { ... }

Оказывается, что код, сгенерированный for (var : object) синтаксис очень похож на предложенный Кори Нельсоном. (Поэтому я бы посоветовал ему и вам просто использовать последний вариант.)

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

Единственный способ перебрать вектор, который может быть изменен во время итерации, — это использовать целочисленные индексы, как в оригинальном посте. Другие контейнеры более щадящие. Вы можете выполнить итерацию карты STL с помощью цикла, который вызывает object.end () на каждой итерации, и (насколько я знаю) она будет работать даже в условиях вставок и удалений, но не пытайтесь делать это с unordered_map, или вектор. Он работает с декой, если вы всегда толкаете в конце и высовываете вперед, что удобно, если вы используете деку в качестве очереди в обходе шириной; Я не уверен, что ты можешь избежать неприятностей с сованием задней части.

Где-то действительно должна быть простая сводка о влиянии модификаций контейнера по типу контейнера на итераторы и указатели элементов (которые не всегда совпадают с итераторами), так как все это определено стандартом, но я никогда не сталкивался с этим в любом месте. Если найдешь, дай мне знать.

11

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

3

Важные предупреждения могут быть потеряны в потоке предупреждений о сравнениях со знаком и без знака для переменных цикла. И даже некоторые подписанные / неподписанные предупреждения сравнения важны! Итак, избавьтесь от неважных предупреждений, определив функцию размера, например:

#include <stddef.h>    // ptrdiff_t
#include <utility>     // std::begin, std::end

typedef ptrdiff_t Size;
typedef Size Index;

template< class Type >
Size nElements( Type const& c )
{
using std::begin;  using std::end;
return end( c ) - begin( c );
}

Тогда вы можете просто написать, например,

for( int i = 0;  i < nElements( v );  ++i ) { ... }

С другой стороны, использовать итераторы, например

for( auto it = begin( v );  it != end( v );  ++it ) { ... }

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

for( auto const& elem : v ) { ... }

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

Другая область, на которую вам следует обратить внимание, это броски в стиле C: избавьтесь от них! 😉

2
По вопросам рекламы [email protected]