Тип int по умолчанию: подписанный или неподписанный?
При программировании на C-подобном языке должен ли быть целочисленный тип по умолчанию int или uint/unsigned int? По умолчанию я имею в виду, когда вам не нужны отрицательные числа, но любое из них должно быть достаточно большим для данных, которые вы держите. Я могу придумать хорошие аргументы для обоих:
подписано: математически лучше себя ведет, меньше вероятность странного поведения, если вы пытаетесь пойти ниже нуля в некотором граничном случае, о котором вы не думаете, как правило, лучше избегать нечетных угловых случаев.
unsigned: обеспечивает дополнительную защиту от переполнения, на случай, если ваши предположения о значениях неверны. В качестве документации служит то, что значение, представленное переменной, никогда не должно быть отрицательным.
7 ответов
Руководство по стилю Google C++ имеет интересное мнение о целых числах без знака:
(цитата следует:)
На неподписанных целых числах
Некоторые люди, включая некоторых авторов учебников, рекомендуют использовать неподписанные типы для представления чисел, которые никогда не бывают отрицательными. Это задумано как форма самодокументирования. Однако в Си преимущества такой документации перевешиваются реальными ошибками, которые она может внести. Рассматривать:
for (unsigned int i = foo.Length()-1; i >= 0; --i) ...
Этот код никогда не прекратит работу! Иногда gcc заметит эту ошибку и предупредит вас, но часто этого не будет. Одинаково плохие ошибки могут возникать при сравнении знаковых и неподписанных переменных. По сути, схема продвижения типов в C приводит к тому, что неподписанные типы ведут себя иначе, чем можно было бы ожидать.
Итак, документируйте, что переменная неотрицательна, используя утверждения. Не используйте неподписанный тип.
(конец цитаты)
Конечно, подписано. Если переполнение беспокоит вас, оно должно беспокоить вас больше, потому что случайное падение "ниже нуля" легче, чем через int-max.
"unsigned" должен быть осознанным выбором, который заставляет разработчика думать о потенциальных рисках, который используется только там, где вы абсолютно уверены, что вы никогда не можете пойти на отрицание (даже случайно), и что вам нужно дополнительное пространство значений.
Как грубое правило, я использовал беззнаковые целые для подсчета вещей и подписанные целые для измерения вещей.
Если вы обнаружите, что вы уменьшаете или вычитаете из целого числа без знака, то вы должны быть в контексте, в котором вы уже ожидаете, что будете очень осторожны, чтобы не потерять (например, потому что вы находитесь в каком-то низкоуровневом коде, отступающем от конца строки, поэтому, конечно, вы сначала убедитесь, что строка достаточно длинна, чтобы поддерживать это). Если вы не находитесь в таком контексте, где абсолютно важно, чтобы вы не опускались ниже нуля, тогда вы должны были использовать значение со знаком.
В моем использовании целые числа без знака предназначены для значений, которые абсолютно не могут стать отрицательными (или для той, которая в миллионной ситуации, когда вы на самом деле хотите арифметику по модулю 2^N), а не для значений, которые просто не оказываются отрицательными, в текущей реализации, наверное.
Я склонен идти с подписью, если я не знаю, мне нужно без подписи, как int
обычно подписывается, и для ввода текста требуется больше усилий unsigned int
, а также uint
может заставить другого программиста сделать небольшую паузу, чтобы подумать, какими могут быть значения.
Таким образом, я не вижу никакой выгоды просто по умолчанию для unsigned, так как обычное int подписано.
Вы не получаете много "гарантии от переполнения" с неподписанным. Вы, скорее всего, получите другое, но более странное поведение, чем с подписанным, но чуть позже... Может быть, лучше получить эти предположения прямо перед рукой?
Предоставление более конкретного присваивания типа (например, unsigned int) передает больше информации об использовании переменной и может помочь компилятору отслеживать любые моменты времени, когда вы присваиваете "неправильное" значение. Например, если вы используете переменную для отслеживания идентификатора базы данных объекта / элемента, то (вероятно) никогда не должно быть времени, когда идентификатор меньше нуля (или единицы); в этом случае вместо утверждения этого состояния использование целочисленного значения без знака передает это утверждение другим разработчикам, а также компилятору.
Я сомневаюсь, что есть действительно хороший не зависящий от языка ответ на это. Между языками и тем, как они работают со смешанными типами, достаточно различий, поэтому ни один ответ не будет иметь смысла для всех (или даже большинства).
На языках, которые я использую чаще всего, я использую подпись, если у меня нет особых причин поступать иначе. Это в основном C и C++, хотя. На другом языке я вполне мог бы дать другой ответ.