"无符号整数具有一些奇怪的属性,除非你有充分的理由,否则应该避免使用它们。获得1个额外的正数位或表达一个值不能为负的约束条件,都
不是好的理由。
无符号整数实现算术运算
模UINT_MAX+1
。相比之下,有符号整数的操作表示我们从学校里熟悉的自然算术。
溢出语义
无符号整数具有定义良好的溢出;有符号整数则没有:"
unsigned u = UINT_MAX
u++
int i = INT_MAX
i++
这意味着,在测试期间可以捕获有符号整数溢出,而无符号溢出可能会默默地执行错误的操作。因此,只有在确定要合法化溢出时才使用
unsigned
。
如果您有一个值不能为负数的约束条件,则需要一种方法来检测和拒绝负值;
int
非常适合这个任务。
unsigned
将接受负值并将其静默地溢出为正值。
位移语义
对于不大于数据类型中位数的数量的
unsigned
位移,始终定义良好。在C++20之前,如果
signed
位移导致符号位中的1向左移动,它是未定义的,如果导致符号位中的1向右移动,则是实现定义的。自C++20以来,
signed
右移始终保留符号,但
signed
左移则不保留符号。因此,对于某些位操作,请使用
unsigned
。
混合符号操作
内置算术运算始终对相同类型的操作数进行操作。如果提供了不同类型的操作数,则“通常的算术转换”将它们强制转换为相同的类型,有时会产生令人惊讶的结果:
unsigned u = 42;
std::cout << (u * -1); // 4294967254
std::cout << std::boolalpha << (u >= -1); // false
什么是不同之处?
从一个无符号值中减去另一个无符号值会产生一个无符号结果,这意味着2
和1
之间的差值是4294967295
。
最大值加倍
int
类型在表示数值时使用一个比特位来表示其符号。unsigned
类型则将该比特位作为另一个数字比特位来使用。因此,通常情况下,int
有31个数字比特位,而unsigned
有32个。这个额外的比特位经常被认为是使用unsigned
类型的原因之一。但是,如果31个比特位对于某个特定目的来说是不够的,则很可能32个比特位也不够用,你应该考虑64位或更多。
函数重载
从int
到unsigned
的隐式转换与从int
到double
的转换具有相同的级别,因此以下示例是非法的:
void f(unsigned);
void f(double);
f(42); // error: ambiguous call to overloaded function
互操作性
许多API(包括标准库)使用unsigned
类型,通常是出于错误的原因。与这些API交互时,使用unsigned
是明智的选择,以避免混合符号运算。
附录
引用的代码片段包括表达式0 <= grade <= 100
。它将首先评估0 <= grade
,这始终为true
,因为grade
不能为负数。然后它将评估true <= 100
,这始终为true
,因为true
转换为整数1
,并且1 <= 100
是true
。
unsigned
实际上会增加代码的复杂度。为了使其工作,应该使用cin >> astring
将输入读入字符串,然后使用strtoul
或std::stoul
将字符串转换为无符号数据类型。这里可能更喜欢使用strtoul
,因为它可以处理退出条件而不抛出异常。 - user45813010 <= grade <= 100
可能不会产生你期望的结果。我记得有些编译器也会对这种错误发出警告,但我可能记错了... - anderas