如果您知道一个变量永远不会为负数,将其声明为无符号变量是否很重要?这有助于防止输入到不应该接受负数的函数中的除负数以外的任何其他内容吗?
在语义上非负的值,使用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++中,这个问题以多种不同的方式表现出来(例如使用“滑动指针”技术进行数组的向后迭代,使用迭代器进行标准容器的向后迭代)。也就是说,无论上述无符号循环对你来说可能看起来多么不优雅,都没有办法完全避免它们,即使你从不使用无符号整数类型。因此,最好学习这些技术,并将它们包含到您已经建立的惯用语集中。
它不能防止人们滥用您的接口,但至少他们应该会得到一个警告,除非他们添加了C风格的转换或static_cast
来使其消失(在这种情况下,您无法再帮助他们)。
是的,这样做有价值,因为它正确地表达了您所希望的语义。
一个小细节是它可以减少可能需要的数组边界检查测试量......例如,不必再写:
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]);
[...]
返回一个负值,你将在第一种情况下捕获错误。然而,在第二种情况下,你将无法捕获错误,但是你将使用另一个由无符号包装行为产生的“随机”正索引。这更糟糕。 - Johannes Schaub - litblen
而没有上限,那该怎么办?应用此答案中所应用的原则意味着您不再进行任何检查,然后对该大长度进行操作。这样不好。 - Johannes Schaub - litbstruct 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,毕竟,您很可能不需要窗口足够大才需要将其设置为unsigned。
r.x
是负数,则r.x + r.width
将导致完全奇怪的结果:例如,-5 + 4u
会得到UINT_MAX
。 - Johannes Schaub - litb它有两个作用:
1)为无符号值提供双倍的范围。当“有符号”时,最高位被用作符号位(1表示负数,0表示正数),当“无符号”时,您可以使用该位进行数据。例如,char
类型从-128到127,unsigned char
类型从0到255。
2)它影响>>
运算符的操作,特别是右移负值时的操作。
unsigned
的目的是要表明变量只能存储非负值,则需要进行某种检查。否则,你所得到的只有:
assert((idx >= 0) && "Index must be greater/equal than 0!");
// assume idx is unsigned. What if idx is 0 !?
if(idx - 1 > 3) /* do something */;
unsigned
并不能使 assert
消失,但它使条件更简单:x < max
而不是 0 <= x and x < max
(或者,可以用一次断言代替两次)。就我个人而言,这是支持 unsigned
的一个非常有力的论据。 - Konrad Rudolphunsigned
将使函数无法捕获错误,因为函数中的参数根据定义始终为正数。编译器不能在所有情况下警告您,有时仅在调用方进行unsigned
转换以消除警告将无法修复任何错误 - 相反,负值将会静默地环绕。 - Johannes Schaub - litbidx
是无符号的,并且您依赖于环绕,则会认为 UINT_MAX
是负数 -1
。因此,您会将一半的 unsigned
范围丢弃以将负值检测为更高一半的正值。这完全是错误的。正确的方法是在它们为负时检测负值。否则,unsigned
对您来说什么都没有,甚至不是正范围的两倍。 - Johannes Schaub - litbconst
正确性”具有相同的价值。如果您知道某个值不应更改,请将其声明为const
并让编译器帮助您。如果您知道一个变量应始终为非负数,则将其声明为unsigned
,编译器将帮助您捕获不一致之处。
(这样做还可以在此上下文中使用unsigned int
而不是int
来表示两倍大的数字。)它还可以避免您在与其他接口交互时不得不进行无符号转换。例如:
for (int i = 0; i < some_vector.size(); ++i)
这通常会让任何需要在没有警告的情况下编译的人感到非常恼火。
当不需要使用有符号值时,使用无符号值可以确保数据类型不表示低于所需下限的值,并增加最大上限。所有原本用于表示负数的位组合都用于表示更大的正数集。
i --> 0
。 - Potatoswatterv.size()==0
时v.size()-1
的结果将为UINT_MAX
(或类似高值),导致循环错误 :(. 我认为std::vector<>::size_type
是无符号的,这是一个糟糕的设计,并且我不信任 C++ 标准库的设计 - 我只需要看看vector<bool>
,foo_facet
和所有其他“臭名昭著”的事情。 :) 这是我认为 Java 做出了好的设计原则的情况之一。 - Johannes Schaub - litb