Почему int, а не unsigned int используется для циклов C и C ++ для циклов? - PullRequest
40 голосов
/ 20 сентября 2011

Это довольно глупый вопрос, но почему int обычно используется вместо unsigned int при определении цикла for для массива в C или C ++?

for(int i;i<arraySize;i++){}
for(unsigned int i;i<arraySize;i++){}

Я понимаю преимущества использованияint при выполнении чего-либо другого, кроме индексации массива и преимуществ итератора при использовании контейнеров C ++.Это только потому, что это не имеет значения при циклическом просмотре массива?Или я должен избегать всего этого вместе и использовать другой тип, такой как size_t?

Ответы [ 10 ]

35 голосов
/ 08 июня 2014

Использование int более правильно с логической точки зрения для индексации массива.

unsigned семантика в C и C ++ на самом деле не означает «не отрицательно», но больше похожа на «битовую маску» или «по модулю целое число».

Чтобы понять, почему unsigned не подходит для «неотрицательного» числа, рассмотрите

  • Добавляя, возможно, отрицательное целое число к неотрицательному, вы получаете неотрицательное целое число
  • Разница двух неотрицательных целых чисел всегда является неотрицательным целым числом
  • Умножая неотрицательное целое число на отрицательное целое, вы получаете неотрицательный результат

Очевидно, что ни одна из вышеперечисленных фраз не имеет никакого смысла ... но именно так работает семантика C и C ++ unsigned.

Фактически использование типа unsigned для размера контейнеров является ошибкой проектирования C ++, и, к сожалению, мы теперь обречены использовать этот неправильный выбор навсегда (для обратной совместимости). Вам может понравиться имя «unsigned», потому что оно похоже на «неотрицательное», но имя не имеет значения, и то, что считается семантическим ... и unsigned очень далеко от «неотрицательного».

По этой причине при кодировании большинства циклов для векторов моя личная предпочтительная форма:

for (int i=0,n=v.size(); i<n; i++) {
    ...
}

(конечно, предполагая, что размер вектора не меняется во время итерации и что мне действительно нужен индекс в теле, так как в противном случае for (auto& x : v)... лучше).

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

// draw lines connecting the dots
for (size_t i=0; i<pts.size()-1; i++) {
    drawLine(pts[i], pts[i+1]);
}

Приведенный выше код будет иметь проблемы, если вектор pts пуст, потому что pts.size()-1 - это огромное бессмысленное число в этом случае. Работа с выражениями, в которых a < b-1 отличается от a+1 < b даже для часто используемых значений, похожа на танцы на минном поле.

Исторически оправданием наличия size_t unsigned является возможность использовать дополнительный бит для значений, например, возможность иметь 65535 элементов в массивах вместо просто 32767 на 16-битных платформах. По моему мнению, даже в то время дополнительная стоимость этого неправильного семантического выбора не стоила выигрыша (а если 32767 элементов сейчас недостаточно, тогда 65535 не будет достаточно долго).

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

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

31 голосов
/ 20 сентября 2011

Это более общее явление, часто люди не используют правильные типы для своих целых чисел. Современный C имеет семантические определения типов, которые намного предпочтительнее, чем примитивные целочисленные типы. Например, все, что является «размером», должно быть напечатано как size_t. Если вы систематически используете семантические типы для переменных приложения, переменные цикла также значительно упрощаются с этими типами.

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

4 голосов
/ 20 сентября 2011

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

Конечно, если измерение было прочитано из однобайтовогополе в файле, то вы знаете, что он находится в диапазоне 0-255, и int будет вполне разумным типом индекса.Аналогичным образом, int будет в порядке, если вы зацикливаетесь фиксированное количество раз, например от 0 до 99. Но есть еще одна причина не использовать int: если вы используете i%2 в теле цикла для обработки четных /нечетные индексы иначе, i%2 намного дороже, когда i подписано, чем когда i не подписано ...

4 голосов
/ 20 сентября 2011

Не большая разница. Одним из преимуществ int является его подпись. Таким образом, int i < 0 имеет смысл, в то время как unsigned i < 0 не так много.

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

И да, меньше писать: -)

2 голосов
/ 20 сентября 2011

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

На современных платформах off_t, ptrdiff_t и size_tгарантировать гораздо большую мобильность.

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

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

GCC даже поддерживает расширение typeof, которое освобождает вас от необходимости вводить одинаковое имя типа повсюду:

typeof(arraySize) i;

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

Тогда, если выизмените тип arraySize, тип i изменится автоматически.

0 голосов
/ 24 мая 2017

Рассмотрим следующий простой пример:

int max = some_user_input; // or some_calculation_result
for(unsigned int i = 0; i < max; ++i)
    do_something;

Если max окажется отрицательным значением, скажем, -1, -1 будет считаться UINT_MAX (когда сравниваются два целых числа с рангом sam, но разным значением), будет подписано рассматривается как неподписанный). С другой стороны, следующий код не будет иметь этой проблемы:

int max = some_user_input;
for(int i = 0; i < max; ++i)
    do_something;

Дайте отрицательный max вход, цикл будет безопасно пропущен.

0 голосов
/ 20 сентября 2011

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

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

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

0 голосов
/ 20 сентября 2011

Потому что, если у вас нет массива размером более двух гигабайт типа char или 4 гигабайт типа short или 8 гигабайт типа int и т. Д., На самом деле не имеет значения, если переменная подписанаили нет.

Итак, зачем печатать больше, если можно печатать меньше?

0 голосов
/ 20 сентября 2011

Это действительно зависит от кодера.Некоторые программисты предпочитают перфекционизм типов, поэтому они будут использовать любой тип, с которым сравнивают.Например, если они перебирают строку C, вы можете увидеть:

size_t sz = strlen("hello");
for (size_t i = 0; i < sz; i++) {
    ...
}

Хотя, если они просто что-то делают 10 раз, вы, вероятно, по-прежнему увидите int:

for (int i = 0; i < 10; i++) {
    ...
}
0 голосов
/ 20 сентября 2011

Я использую int, потому что это требует меньше физической типизации и не имеет значения - они занимают одинаковое количество места, и если в вашем массиве нет нескольких миллиардов элементов, вы не переполнитесь, если не используете 16-битный компилятор, которым я обычно не являюсь.

...