您遇到了环绕行为。
无符号类型是循环的(另一方面,有符号类型可能是循环的,也可能不是循环的,但它是您不应该依赖的未定义行为)。也就是说,比最小可能值小一是最大可能值。您可以使用以下 sn-p 自己演示这一点:
int main()
{
unsigned int x = 5;
for (int i = 0; i < 10; ++i) cout << x-- << endl;
return 0;
}
您会注意到,在达到零后,x 的值会跳到 2^32-1,即最大可表示值。减去进一步的行为符合预期。
当你从无符号的 0 中减去 1 时,位模式会发生如下变化:
0000 0000 0000 0000 0000 0000 0000 0000 // before (0)
1111 1111 1111 1111 1111 1111 1111 1111 // after (2^32 - 1)
对于无符号数,负数被视为从零减去正数。所以(unsigned int) -10 将等于((unsigned int) 0) - ((unsigned int) 10)。
我喜欢将其视为无符号整数,它是高精度任意值的最低 32 位。像这样:
v imaginary high order bit
1 0000 0000 0000 0000 0000 0000 0000 0000 // before (2^32)
0 1111 1111 1111 1111 1111 1111 1111 1111 // after (2^32 - 1)
在这些溢出情况下 unsigned int 的行为与从 256 中减去 1 时 unsigned int 的低 8 位的行为完全相同。查看 unsigned char(1 字节)更有意义像这样,因为如果转换为 unsigned char,值 0 和 256 是相等的,因为有限的精度会丢弃额外的位。
0 0000 0000 0000 0000 0000 0001 0000 0000 // before (256)
0 0000 0000 0000 0000 0000 0000 1111 1111 // before (255)
正如其他人所指出的,这称为模运算。使用更高的精度值来帮助可视化环绕工作时所做的转换,因为您屏蔽了高阶位。它是什么并不重要,所以它可以是任何东西,它只是被丢弃。整数是超过模 2^32 的值,因此 2^32 的任何倍数在整数空间中都为零。这就是为什么我可以侥幸假装最后还有一点。
模数运算有自己的专用运算符,以防您需要在程序中计算除 2^32 以外的数字,如以下语句所示:
int forty_mod_twelve = 40 % 12;
// value is 4: 4 + n * 12 == 40 for some whole number n
对 2 的幂(如 2^32)的模运算直接简化为屏蔽高位,如果您取一个 64 位整数并以 2^32 为模计算,则该值将与您完全一样已将其转换为无符号整数。
01011010 01011100 10000001 00001101 11111111 11111111 11111111 11111111 // before
00000000 00000000 00000000 00000000 11111111 11111111 11111111 11111111 // after
程序员喜欢使用这个属性来加速程序,因为很容易砍掉一些位数,但执行模运算要困难得多(它与除法差不多)。
这有意义吗?