为啥以 char 作为索引的循环会无限循环?
Posted
技术标签:
【中文标题】为啥以 char 作为索引的循环会无限循环?【英文标题】:Why does a loop with a char as it's index loop infinitely?为什么以 char 作为索引的循环会无限循环? 【发布时间】:2012-09-02 10:41:31 【问题描述】:这个循环将无限期地继续下去:
char a = 100;
for(a=100; a>=0;--a)
System.out.println(a);
发生这种情况是因为 a 被提升为算术运算的 int 值并从 16 位 char 值扩大到 32 位,因此将始终保持正数吗?
【问题讨论】:
@oldrinb - 只回答“愿意”。更有趣的问题是“为什么”。 @StephenC 我的评论回答了标题中的问题。 “为什么”我在下面回答。 @Phoenix 我已经更新了我的回复,事实上你是对的,原因是...... :-) 【参考方案1】:不。 char
值是无符号的 - 当它们低于 0 时,它们会回到 65535。
将char
替换为byte
- 然后就可以了。
【讨论】:
斯蒂芬,你是对的。我假设它就像 C 的字符。 @NickODell 那么你应该知道 plainchar
的签名是实现定义的......它只保证能够适应整个 ASCII 字符集( 0 - 127),C99 确保它可以存储至少 8 位。
oldrinb,在 C 语言中,我总是明确变量是有符号还是无符号,只是为了确保它在所有编译器上都能正常工作。这对于 int 等是否有必要,或者只是偏执狂?
@NickODell no -- int
对应于signed int
,long
对应于signed long int
,short
对应于signed short int
。 char
的区别在于技术上 plain char
、signed char
和 unsigned char
都是独立的数据类型。【参考方案2】:
它确实会无限循环——你所说的原因很接近。这是因为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 位。由于这里-1为0b11111111 11111111 11111111 11111111,所以只保存了较低的0b11111111 11111111。由于char
是无符号的,所有位都对结果有贡献,得到 65535。
注意到这里的东西了吗?本质上,这一切都意味着 Java 整数运算是模块化的;在这种情况下,模数是 2^16 或 65536,因为 char
是 16 位数据类型。 -1 (mod 65536) ≡ 65535,所以减量会返回。
【讨论】:
这真的是算术下溢吗?参看。 en.wikipedia.org/wiki/Arithmetic_underflow 你还是搞错了。 Java 中确实会发生整数下溢。只是没有表示 ...正如您引用的 JLS 文本所述。也就是说没有抛出异常,也没有什么可以直接测试看是否发生下溢。【参考方案3】:正如其他人所说,Java 中的char
类型是无符号的,因此a >= 0
将始终为真。当a
达到 0 然后再次递减时,它变为 65535。如果你只是想反常,你可以这样编写循环以在 101 次迭代后终止:
char a = 100;
for(a=100; a<=100;--a)
System.out.println(a);
然后,代码审阅者可能会因为写出如此可怕的东西而让您心碎。 :)
【讨论】:
以上是关于为啥以 char 作为索引的循环会无限循环?的主要内容,如果未能解决你的问题,请参考以下文章
为啥当我实现以 2^20 为底的基数排序以对大小为 500 万的数组进行排序时,该程序会陷入无限循环?