为什么一个以字符作为索引的循环会无限循环?

3

这个循环将无限继续:

char a = 100;
for(a=100; a>=0;--a)
    System.out.println(a);

这是因为在算术运算中,a会被提升为int类型的值,并且从16位的char值扩展为32位,因此它始终保持正数。


@oldrinb - 这只回答了“它会不会”的问题。更有趣的问题是“为什么”。 - Stephen C
@StephenC 我的评论回答了标题中的问题。我在下面解释了“为什么”。 - obataku
@Phoenix 我已经更新了我的回复,事实上你说的原因是正确的... :-) - obataku
3个回答

8

它确实会无限循环,而你所述的原因是接近的。它之所以发生是因为a不能表示任何不满足a >= 0的数字--char是无符号的。在Java中,算术下溢是被定义明确的,但没有指示。请参见规范的相关部分如下。

  • §4.2.2

    整数运算符不以任何方式指示溢出或下溢。

    这意味着除了比较值之外,没有任何溢出/下溢的指示...例如,如果 a <= --a,则表示发生了下溢。

  • §15.15.2

    在减法之前,对值1和变量的值执行二进制数字提升(§5.6.2)。如果必要,差异通过缩小原始转换(§5.1.3)和/或转化为类型变量进行封箱(§5.1.7),然后存储。前缀递减表达式的值是变量的值,在新值存储之后。

    因此,我们可以看到这里有两个主要步骤:二进制数字提升,然后是缩小的原始转换。

  • §5.6.2

    按照以下规则转换一种或两种操作数:应用扩展原始转换(§5.1.2):

    • 如果任一操作数为double类型,则将另一个操作数转换为double
    • 否则,如果任一操作数为float类型,则将另一个操作数转换为float
    • 否则,如果任一操作数为long类型,则将另一个操作数转换为long
    • 否则,两个操作数都转换为int类型。

    我们可以看到递减表达式使用a作为int处理,从而执行扩展转换。这使它能够表示值-1

  • §5.1.3

    缩小的原始转换可能会丢失有关数字值总体大小的信息,并且可能会失去精度和范围。

    ...

    将带符号整数缩小为整数类型T只丢弃了除n个最低位之外的所有位,其中n是用于表示类型T的位数。除可能丢失数字值大小的信息外,这还可能导致结果值的符号与输入值的符号不同。

    仅保留n个最低位意味着仅保留int表达式a - 1的最低16位。由于此处的-10b11111111 11111111 11111111 11111111,因此仅保存更低的0b11111111 11111111。由于char是无符号的,所有这些位都对结果有贡献

    注意到这里的问题了吗?本质上,这意味着Java整数算术是模运算;在这种情况下,模数是2^1665536,因为char是一个16位数据类型。-1 (mod 65536) ≡ 65535,所以递减将会回绕。


这真的是算术下溢吗?请参见http://en.wikipedia.org/wiki/Arithmetic_underflow。 - jsj
1
你还是错了。在Java中确实会发生整数下溢,只是没有像你引用的JLS文本所述那样指示它。也就是说,不会抛出异常,也没有直接测试下溢是否发生的方法。 - Stephen C

4

不对。 char 值是无符号的 - 当它们小于 0 时,它们会重新回到 65535。

char 替换为 byte - 然后它就可以工作了。


Stephen,你是正确的。我假设它像C语言中的char一样。 - Nick ODell
1
@NickODell,那么你应该知道plain char的符号是实现定义的...它只保证能够容纳整个ASCII字符集(0-127),而C99确保它至少可以存储8位。 - obataku
在C语言中,我总是明确变量是有符号还是无符号的,只是为了确保它在所有编译器上都能正常工作。即使是int等类型,这样做也是必要的吗,还是只是多虑? - Nick ODell
1
@NickODell 不是的--int 对应 signed intlong 对应 signed long intshort 对应 signed short int。之所以会有区别,是因为从技术上讲,plaincharsigned char unsigned char 都是不同的数据类型。 - obataku

1

正如其他人所说,Java中的char类型是无符号的,因此a >= 0将始终为真。当a达到0并再次递减时,它变成了65535。如果你只是想要刻意违反常规,你可以这样编写你的循环,在101次迭代后终止:

char a = 100;
for(a=100; a<=100;--a)
    System.out.println(a);

代码审查者随后可以玩得很开心,把你因编写如此可怕的东西而批得体无完肤。 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接