将变量声明为无符号的重要性

时间:2010-09-16 18:11:01

标签: c++ variables unsigned

如果你知道变量永远不应该是负数,那么将变量声明为无符号是否很重要?它是否有助于防止除负数以外的任何东西被输入到不应该有它们的函数中?

14 个答案:

答案 0 :(得分:21)

将语义非负值的变量声明为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和C ++中以许多不同的方式表现出来(比如使用迭代器在标准容器上使用反向迭代的“滑动指针”技术对数组进行反向迭代)。即无论上面的无符号周期看起来多么不优,你都无法完全避免它们,即使你从不使用无符号整数类型。因此,最好学习这些技巧并将它们包含在你已建立的惯用语中。

答案 1 :(得分:6)

它不会阻止人们滥用您的界面,但至少他们应该收到警告,除非他们添加C风格的演员或static_cast以使其消失(在这种情况下,您无法进一步帮助他们) 。

是的,因为它正确地表达了你想要的语义,所以它有价值。

答案 2 :(得分:4)

它做了两件事:

1)它为您的无符号值值提供了两倍的范围。当“签名”时,最高位用作符号位(1表示负数,0表示正数),当“无符号”时,您可以将该位用作数据。例如,char类型从-128到127,unsigned char从0到255

2)它影响>>运算符的行为,特别是在右移右值时。

答案 3 :(得分: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]);

答案 4 :(得分:4)

其他答案都很好,但有时会导致混乱。我相信为什么有些语言选择不使用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

当然它仍然无法保护您免受任何整数溢出的影响。所以在这个场景中,即使widthheight在概念上不是负面的,但除了要求某些演员阵容之外,制作unsigned并没有真正的好处。摆脱有关混合有符号和无符号类型的警告。最后,至少在这样的情况下,最好只让它们全部int,最重要的是,你可能没有足够宽的窗口来需要它是unsigned

答案 5 :(得分:4)

  

如果你知道变量永远不应该是负数,那么将变量声明为无符号是否很重要?

当然不重要。有些人(Stroustrup和Scott Meyers,见"Unsigned vs signed - Is Bjarne mistaken?")拒绝认为变量应该是无符号的,因为它代表了无符号数量。如果使用unsigned的意思是指示变量只能存储非负值,则需要以某种方式检查它。否则,你得到的只是

  • 一种静默隐藏错误的类型,因为它不会让负值暴露
  • 相应签名类型的正范围的两倍
  • 定义溢出/位移/等语义

当然,它不会阻止人们为您的函数提供负值,并且编译器将无法警告您任何此类情况(考虑传递的基于负运行时的int值)。为什么不在函数中断言呢?

assert((idx >= 0) && "Index must be greater/equal than 0!");

无符号类型也引入了许多陷阱。在计算中使用它时,必须要小心,这些计算可能暂时小于零(向下计数循环,或某事),尤其是在无符号和有符号值的C和C ++语言中发生的自动升级

// assume idx is unsigned. What if idx is 0 !?
if(idx - 1 > 3) /* do something */;

答案 6 :(得分:2)

这具有与“const正确性”具有价值的原因相同的价值。如果您知道特定值不应该更改,请声明它const并让编译器帮助您。如果您知道变量应始终为非负数,则将其声明为unsigned,编译器将帮助您捕获不一致性。

(如果您在此上下文中使用unsigned int而非int,则可以将数字表示为两倍。)

答案 7 :(得分:1)

通过在不需要有符号值时使用无符号,除了确保数据类型不表示低于所需下限的值之外,还可以增加最大上限。否则将用于表示负数的所有比特组合用于表示更大的正数集。

答案 8 :(得分:1)

它还使您不必在与其他接口交互时转换为无符号的任何内容。例如:

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

这通常会让任何需要在没有警告的情况下编译的人感到厌烦。

答案 9 :(得分:1)

它不会阻止负数被输入函数;相反,它会将它们解释为大的正数。如果您知道错误检查的上限,这可能是适度有用的,但您需要自己进行错误检查。有些编译器会发出警告,但是如果你使用的是无符号类型,可能会有太多警告可以轻松处理。这些警告可以用强制转换来掩盖,但这比只坚持签名类型更糟糕。

如果我知道变量不应该是负数,我就不会使用无符号类型,而是如果它不可能。例如,size_t是无符号类型,因为数据类型不能具有负大小。如果某个值可能是负数但不应该是负数,那么通过将其作为有符号类型并使用i < 0i >= 0之类的内容来表达更为容易(这些条件为{{1}如果false是无符号类型,则分别为{}和true,无论其值如何。)

如果您担心严格的标准一致性,那么知道无符号算术中的溢出是完全定义的可能是有用的,而在带符号算术中它们是未定义的行为。

答案 10 :(得分:1)

混合有符号和无符号类型可能是一个令人头痛的问题。生成的代码通常会变得臃肿,错误或两者兼而有之(*)。在许多情况下,除非您需要在32位变量中存储2,147,483,648和4,294,967,295之间的值,或者您需要使用大于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的旧Pascal会通过将SignedVar和UnsignedVar转换为更大的签名类型(最大支持类型,btw,已签名,因此每个无符号类型都可以转换为更大的签名类型)来处理上述场景。这会产生很大的代码,但这是正确的。在C中,如果一个有符号变量为负数,则即使UnsignedVar为零,结果也可能在数值上错误。还存在许多其他不良情况。

答案 11 :(得分:1)

使用unsigned提供的两件主要内容

  • 它允许您对任何值安全地使用右移>>运算符,因为它不能为负值 - 使用负值的右移是未定义的。

    < / LI>
  • 它为你提供了环绕mod 2 ^ n算术。对于带符号的值,下溢/溢出的影响是不确定的。使用无符号值,您可以得到mod 2 ^ n算术,因此0U - 1U将始终为您提供最大可能的无符号值(总是比2的幂小1)。

答案 12 :(得分:0)

使用unsigned的一个反驳论点是,你可能会发现自己处于非常合理的情况,它会引入尴尬和无意的错误。考虑一个类 - 例如列表类或一些类 - 使用以下方法:

unsigned int length() { ... }

似乎很合理。但是当你想迭代它时,你会得到以下结果:

for (unsigned int i = my_class.length(); i >= 0; --i) { ... }

你的循环不会终止,现在你被迫施放或做其他一些尴尬。

使用unsigned的替代方法只是assert您的值非负值。

Reference

答案 13 :(得分:-1)

顺便说一句,我很少使用intunsigned int,而是使用{int16_tint32_t,...}或{uint16_tuint32_t,...}。 (你必须包括stdint.h才能使用它们。)我不确定我的同事是如何找到它的,但我试着以这种方式传达变量的大小。在某些地方,我尝试通过做一些事情来表现得更加明显:typedef uint32_t Counter32;