Важно ли объявлять переменную как unsigned, если вы знаете, что она никогда не должна быть отрицательной? Помогает ли это предотвратить включение в функцию, отличную от отрицательных, функций, которые не должны иметь их?
Важность объявления переменной как unsigned
Ответ 1
Объявление переменных для семантически неотрицательных значений unsigned
- хороший стиль и хорошая практика программирования.
Однако имейте в виду, что это не мешает вам делать ошибки. Если совершенно законно назначать отрицательные значения целым без знака, со значением, которое неявно преобразуется в неподписанную форму в соответствии с правилами беззнаковой арифметики. Некоторые компиляторы могут выдавать предупреждения в таких случаях, другие будут делать это тихо.
Также стоит отметить, что работа с целыми числами без знака требует знания некоторых специальных неподписанных методов. Например, "классический" пример, который часто упоминается в связи с этой проблемой, - это обратная итерация
for (int i = 99; i >= 0; --i) {
/* whatever */
}
Вышеуказанный цикл выглядит естественным с подписью i
, но он не может быть напрямую преобразован в неподписанную форму, что означает, что
for (unsigned i = 99; i >= 0; --i) {
/* whatever */
}
действительно не выполняет то, что он предназначен (на самом деле это бесконечный цикл). Правильная техника в этом случае либо
for (unsigned i = 100; i > 0; ) {
--i;
/* whatever */
}
или
for (unsigned i = 100; i-- > 0; ) {
/* whatever */
}
Это часто используется как аргумент против неподписанных типов, т.е. предположительно, приведенные выше неподписанные версии цикла выглядят "неестественными" и "нечитаемыми". В действительности, хотя проблема, которую мы имеем здесь, является общей проблемой работы вблизи левого конца замкнутого диапазона. Эта проблема проявляется по-разному в C и С++ (например, обратная итерация по массиву с использованием метода "скользящего указателя" обратной итерации по стандартным контейнерам с использованием итератора). То есть независимо от того, как неэлегантные вышеперечисленные беззнаковые циклы могут выглядеть для вас, не существует способа избежать их полностью, даже если вы никогда не используете неподписанные целые типы. Итак, лучше изучить эти методы и включить их в свой набор установленных идиом.
Ответ 2
Это не мешает людям злоупотреблять вашим интерфейсом, но по крайней мере они должны получить предупреждение, если они не добавят листинг C-стиля или static_cast
, чтобы он ушел (в этом случае вы не сможете помочь им в дальнейшем).
Да, в этом есть значение, поскольку оно правильно выражает семантику, которую вы хотите.
Ответ 3
Он выполняет две функции:
1) Это дает вам двойной диапазон значений без знака. Когда "подписан" самый старший бит используется в качестве знакового бита (1 означает отрицательный, 0 для положительного), когда "без знака" вы можете использовать этот бит для данных. Например, тип char
имеет значение от -128 до 127, a unsigned char
принимает форму от 0 до 255
2) Это влияет на то, как действует оператор >>
, особенно при смещении отрицательных значений справа.
Ответ 4
Одна незначительная тонкость заключается в том, что она сокращает количество проверок проверки границ массивов, которые могут быть необходимы... например. вместо того, чтобы писать:
int idx = [...];
if ((idx >= 0)&&(idx < arrayLength)) printf("array value is %i\n", array[idx]);
вы можете просто написать:
unsigned int idx = [...];
if (idx < arrayLength) printf("array value is %i\n", array[idx]);
Ответ 5
Другие ответы хороши, но иногда это может привести к путанице. Я полагаю, почему некоторые языки предпочитают не иметь unsigned
целых типов.
Например, предположим, что у вас есть структура, которая выглядит так, чтобы представлять экранный объект:
struct T {
int x;
int y;
unsigned int width;
unsigned int height;
};
Идея состоит в том, что невозможно иметь отрицательную ширину. Ну, какой тип данных вы используете для хранения правого края прямоугольника?
int right = r.x + r.width; // causes a warning on some compilers with certain flags
и, конечно, он по-прежнему не защитит вас от каких-либо целых переполнений. Итак, в этом сценарии, хотя width
и height
не могут быть концептуально отрицательными, нет никакого реального выигрыша в их создании unsigned
, за исключением того, что требуется, чтобы некоторые отбрасывания избавлялись от предупреждений о смешивании подписанных и неподписанных типов. В конце концов, по крайней мере для таких случаев лучше всего сделать их всех int
s, в конце концов, вероятность того, что у вас нет достаточно широкого окна, чтобы нуждался, он был unsigned
.
Ответ 6
Важно ли объявлять переменную как unsigned, если вы знаете, что она никогда не должна быть отрицательной?
Конечно, это не важно. Некоторые люди (Stroustrup и Scott Meyers, см. "Без подписей - подписан ли Бьярн?" ) отвергают идею о том, что переменная должна быть неподписанной потому что он представляет собой неподписанное количество. Если точкой использования unsigned
было бы указание, что переменная может хранить только неотрицательные значения, вам нужно как-то это проверить. В противном случае все, что вы получаете, это
- Тип, который скрывает скрытые ошибки, потому что он не позволяет отрицательным значениям выставлять
- Двойной из положительного диапазона соответствующего подписанного типа
- Определенная семантика переполнения/бит-сдвига/etc
Конечно, это не мешает людям предоставлять отрицательные значения вашей функции, и компилятор не сможет предупредить вас о таких случаях (подумайте о передаче минус-минус на основе встроенной среды). Почему бы не утверждать в функции вместо этого?
assert((idx >= 0) && "Index must be greater/equal than 0!");
Беззнаковый тип вводит много ошибок. Вы должны быть осторожны, когда используете его в вычислениях, которые могут быть временными, меньше нуля (down counting loop или что-то еще), и особенно автоматические рекламные акции, которые происходят на языках C и С++ среди неподписанных и подписанных значений
// assume idx is unsigned. What if idx is 0 !?
if(idx - 1 > 3) /* do something */;
Ответ 7
Это значение имеет значение по той же причине, что "const
correctness" имеет значение. Если вы знаете, что определенное значение не должно меняться, объявите его const
и дайте компилятору помочь вам. Если вы знаете, что переменная всегда должна быть неотрицательной, тогда объявите ее как unsigned
, и компилятор поможет вам уловить несоответствия.
(Это, и вы можете выразить числа в два раза больше, если вы используете unsigned int
, а не int
в этом контексте.)
Ответ 8
Используя unsigned, когда не будут нужны знаковые значения, в дополнение к тому, чтобы тип данных не представлял значения ниже требуемой нижней границы, вы увеличиваете максимальную верхнюю границу. Все битовые комбинации, которые в противном случае использовались для представления отрицательных чисел, используются для представления большего набора положительных чисел.
Ответ 9
Это также не дает вам отбрасывать на/из неподписанных при взаимодействии с другими интерфейсами. Например:
for (int i = 0; i < some_vector.size(); ++i)
Это, как правило, раздражает всех, кто должен компилироваться без предупреждений.
Ответ 10
Он не будет препятствовать подаче отрицательных чисел в функцию; вместо этого он будет интерпретировать их как большие положительные числа. Это может быть умеренно полезным, если вы знаете верхнюю границу для проверки ошибок, но вам нужно выполнить проверку ошибок самостоятельно. Некоторые компиляторы выдают предупреждения, но если вы используете неподписанные типы, может быть слишком много предупреждений, чтобы справиться с ними легко. Эти предупреждения могут быть покрыты листами, но это хуже, чем придерживаться только подписанных типов.
Я бы не использовал неподписанный тип, если бы знал, что переменная не должна быть отрицательной, но, если она не может быть. size_t
является неподписанным типом, например, поскольку тип данных просто не может иметь отрицательный размер. Если значение может быть отрицательным, но не должно быть, легче выразить это, указав его как подписанный тип и используя что-то вроде i < 0
или i >= 0
(эти условия выходят как false
и true
соответственно если i
является неподписанным типом, независимо от его значения).
Если вас беспокоит строгая стандартная совместимость, может быть полезно знать, что переполнения в беззнаковой арифметике полностью определены, а в арифметике с подписью - undefined.
Ответ 11
Смешивание типов с подписью и без знака может быть основной головной болью. Полученный код часто будет раздутым, неправильным или обоими (*). Во многих случаях, если вам не нужно хранить значения между 2,147,483,648 и 4,294,967,295 в 32-битной переменной, или вам нужно работать со значениями, превышающими 9,223,372,036,854,775,807, я бы рекомендовал вообще не беспокоиться о неподписанных типах.
(*) Что должно произойти, например, если программист делает:
{ Question would be applicable to C, Pascal, Basic, or any other language } If SignedVar + UnsignedVar > OtherSignedVar Then DoSomething;
Я считаю, что Borland old Pascal обработал бы вышеупомянутый сценарий, преобразовывая SignedVar и UnsignedVar в более крупный подписанный тип (самый большой поддерживаемый тип, битва, был подписан, поэтому каждый беззнаковый тип может быть преобразован в более крупный подписанный). Это создаст большой код, но это будет правильно. В C, если одна подписанная переменная отрицательна, результат, вероятно, будет иметь числовое значение, даже если UnsignedVar имеет нуль. Также существуют многие другие плохие сценарии.
Ответ 12
Есть две основные вещи, использующие unsigned
:
-
он позволяет безопасно использовать оператор shift shift
>>
для любого значения, поскольку он не может быть отрицательным - использование сдвига вправо по отрицательному значению undefined. -
он дает вам арифметику mod 2 ^ n. При значениях со знаком эффект переполнения/переполнения составляет undefined. С неподписанными значениями вы получаете mod 2 ^ n арифметику, поэтому
0U - 1U
всегда даст вам максимально возможное значение без знака (которое всегда будет 1 меньше, чем 2).
Ответ 13
Аргумент счетчика для использования unsigned
заключается в том, что вы можете оказаться в очень разумных ситуациях, когда возникают неудобные и непреднамеренные ошибки. Рассмотрим класс - например, класс списка или некоторые из них - со следующим методом:
unsigned int length() { ... }
Кажется очень разумным. Но затем, когда вы хотите перебрать его, вы получите следующее:
for (unsigned int i = my_class.length(); i >= 0; --i) { ... }
Ваша петля не закончится, и теперь вы вынуждены бросать или делать какую-то другую неловкость.
Альтернативой использованию unsigned
является только assert
, что ваши значения неотрицательны.
Ответ 14
В стороне, я редко использую int
или unsigned int
, вместо этого я использую либо {int16_t
, int32_t
,...}, либо {uint16_t
, uint32_t
,...}. (Вы должны включить stdint.h
, чтобы использовать их.) Я не уверен, как мои коллеги находят это, но я пытаюсь передать размер переменной таким образом. В некоторых местах я стараюсь быть более вопиющим, делая что-то вроде: typedef uint32_t Counter32;