Почему int, а не unsigned int используется для циклов C и С++?

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

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

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

+34
источник поделиться
10 ответов

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

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

+29
источник

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

unsigned semantic в C и С++ на самом деле не означает "не отрицательный", но это больше похоже на "битмаску" или "по модулю целое".

Чтобы понять, почему unsigned не является хорошим типом для "неотрицательного" номера, пожалуйста, рассмотрите

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

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

Фактически использование типа unsigned для размера контейнеров является ошибкой дизайна С++ и, к сожалению, мы теперь обречены использовать этот неправильный выбор навсегда (для обратной совместимости). Вам может понравиться имя "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 не хватит на долгое время).

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

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

+29
источник
другие ответы

Связанные вопросы


Похожие вопросы

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

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

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

+4
источник

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

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

+3
источник

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

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

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

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

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

typeof(arraySize) i;

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

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

+2
источник

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

0
источник

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

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

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

for (int i = 0; i < 10; i++) {
    ...
}
0
источник

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

Итак, зачем вводить больше, когда вы можете ввести меньше?

0
источник

Помимо того, что он короче для ввода, причина в том, что он позволяет отрицательные числа.

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

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

0
источник

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

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
источник

Посмотрите другие вопросы по меткам или Задайте вопрос