Выбор типа индексных переменных

11

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

std::vector<int> vec;
....

for(int i = 0; i < vec.size(); ++i)
....

Это заставит компилятор выдавать предупреждение о смешанном использовании переменных со знаком и без знака. если я сделаю индексную переменную как for( size_t i = 0; i < vec.size(); i++ ), (или unsigned int), это решит проблемы.

Когда речь идет о более конкретном использовании типов окон, большинство API-интерфейсов Windows имеют дело с DWORD (который определен как unsigned long).

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

DWORD dwCount;
....

for(DWORD i = 0; i < dwCount; ++i)
....

Я нахожу это немного странным. Это может быть проблема с восприятием.

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

_int64 i64Count; // 
....

for(_int64 i = 0; i < i64Count; ++i)
....

Но в случае DWORD, или целых чисел без знака, есть ли проблемы при переписывании его как

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

Как большинство людей работают с похожими проблемами?

Сарат
источник
4
Зачем вам использовать целое число со знаком для представления индекса? Это похоже на использование вектора целых чисел для хранения строки.
Шимон Тот
3
@Let_Me_Be: потому что это облегчает тестирование граничных условий. Например, при отсчете до нуля, тест меньше, чем до выполнения тела цикла, не может работать со значением без знака. Точно так же отсчет до максимума не работает. Конечно, в этом случае целое число со знаком также не будет работать (потому что оно не может представлять такое большое значение).
Иттрилл
«Это заставит компилятор выдавать предупреждение о смешанном использовании подписанных / неподписанных переменных». Это только одна из двух проблем, с которыми вам придется бороться. Во многих случаях std::size_tэто более высокий ранг, чем int (или даже long). Если размер вектора когда-либо превысит std::numeric_limits<int>::max(), вы пожалеете, что использовали int.
Эдриан Маккарти

Ответы:

11

Vector имеет typedef, который говорит вам правильный тип для использования: -

for(std::vector<int>::size_type i = 0; i < thing.size(); ++i)
{
}

Это почти всегда определяется как size_t, но вы не можете полагаться на это

Johnb
источник
8
Не совсем улучшение читабельности, ИМХО.
Док Браун
Нет, но так как это единственный способ определить, какой правильный тип использовать для индекса в векторе, это не имеет значения ...
JohnB
4
В эти дни в C ++ 11 просто использовать авто
JohnB
6
@JohnB Ты имеешь в виду как auto i = 0? Это совсем не помогает, iстань int.
Зенит
1
Читаемость может быть улучшена, с using index_t = std::vector<int>::size_type;.
Тоби Спейт
4
std::vector<int> vec;

for(int i = 0; i < vec.size(); ++i)

Для этого используйте итератор, а не forцикл.

Для других, пока тип переменной имеет одинаковый размер, static_castдолжен работать нормально (то есть, DWORDчтобы int16_t)

Демиан Брехт
источник
2
for (std::vector<int>::iterator i = vec.begin(); i != vec.end(); ++i)это боль писать. Наличие for (auto i = vec.begin();...намного более читабельно. Конечно, foreachтакже в C ++ 11.
Дэвид Торнли
3

Случай, который вы описали, тоже не нравится в C ++. Но я научился жить с этим, либо используя

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

или же

for( int i = 0; i < (int)vec.size(); i++ )

(конечно, последнее только тогда, когда нет риска получить некоторое переполнение int).

Док Браун
источник
3

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

В вашем примере (по сравнению intс size_t) intбудет неявно преобразован в size_t(если только у intнего не будет большего диапазона, чем size_t). Таким образом, если значение intотрицательное, оно, вероятно, будет больше значения, с которым вы его сравниваете, из-за переноса. Это не будет проблемой, если ваш индекс никогда не будет отрицательным, но вы все равно получите это предупреждение.

Вместо этого используйте неподписанный тип (например unsigned int, size_tили, как рекомендует John B , std::vector<int>::size_type) для индексной переменной:

for(unsigned int i = 0; i < vec.size(); i++)

Будьте осторожны при обратном отсчете:

for(unsigned int i = vec.size()-1; i >= 0; i--) // don't do this!

Выше не будет работать, потому что i >= 0всегда верно, когда iне подписано. Вместо этого используйте « оператор стрелки » для циклов, которые ведут обратный отсчет:

for (unsigned int i = vec.size(); i-- > 0; )
    vec[i] = ...;

Как указывают другие ответы, вы обычно хотите использовать итератор для обхода vector. Вот синтаксис C ++ 11:

for (auto i = vec.begin(); i != vec.end(); ++i)
Джои Адамс
источник
1
Это все еще рискует, что unsigned intне достаточно большой, чтобы держать размер.
Эдриан Маккарти
2

Новая опция для C ++ 11, вы можете делать такие вещи, как следующие

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

и

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

Хотя он повторяет чуть больше, чем это делает базовый цикл for, он не столь многословен, как до -11 способов определения значений, и последовательное использование этого шаблона будет работать для большинства, если не для всех возможных терминов, которые вы могли бы хочу сравнить с, что делает его отличным для рефакторинга кода. Это даже работает для простых случаев, подобных этому:

int x = 3; int final = 32; for(decltype(final) i = x; i < final; ++i)

Кроме того, хотя вы должны использовать autoвсякий раз, когда вы устанавливаете iкакое-либо интеллектуальное значение (например vec.begin()), оно decltypeработает, когда вы устанавливаете постоянную, например, ноль, где auto просто разрешит это значение, intпотому что 0 - это простой целочисленный литерал.

Если честно, я хотел бы увидеть механизм компилятора для расширения определения autoтипа для инкрементаторов цикла, чтобы посмотреть на сравниваемое значение.

Матиас
источник
1

Я использую приведение к int, как в for (int i = 0; i < (int)v.size(); ++i). Да, это некрасиво. Я виню в этом глупый дизайн стандартной библиотеки, где они решили использовать целые числа без знака для представления размеров. (Чтобы ... что? Расширить диапазон на один бит?)

zvrba
источник
1
В какой ситуации значительный размер коллекции будет иметь смысл? Использование целых чисел без знака для различных размеров коллекции кажется мне разумным выбором. Последнее, что я проверил, взяв длину строки, тоже редко возвращал отрицательный результат ...
CVn
1
В какой ситуации было бы целесообразно провести простой тест, например, if(v.size()-1 > 0) { ... }вернуть true для пустого контейнера? Проблема в том, что размеры также часто используются в арифметике, особенно. с индексными контейнерами, что вызывает проблемы, учитывая, что они не подписаны. По сути, использование типов без знака для чего-либо еще, кроме 1) побитовых манипуляций или 2) модульной арифметики, вызывает проблемы.
Зврба
2
хорошая точка зрения. Хотя я на самом деле не вижу смысла в вашем конкретном примере (вероятно, я бы просто написал, if(v.size() > 1) { ... }поскольку это проясняет намерение, а в качестве дополнительного бонуса проблема со знаком / без знака становится недействительной), я вижу, как в некоторых конкретных случаях Подписание может быть полезным. Я стою исправлено.
CVN
1
@Michael: я согласен, это был надуманный пример. Тем не менее, я часто пишу алгоритмы с вложенными циклами: for (i = 0; i <v.size () - 1; ++ i) для (j = i + 1; j <v.size (); ++ j) .. если v пусто, внешний цикл выполняется (size_t) -1 раз. Поэтому я должен либо проверить v.empty () перед циклом, либо привести v.size () к подписанному типу, оба из которых я лично считаю уродливыми обходными путями. Я выбираю приведение, так как меньше LOC, нет, если () s => меньше возможностей для ошибки. (Кроме того, во 2-м дополнении oveflow преобразования возвращает отрицательное число, поэтому цикл вообще не выполняется.)
zvrba
Расширение диапазона на 1 бит было (и остается) очень полезным в 16-битных системах.
Адриан МакКарти