虽然用goto实现所有都很容易(如f.ex.IL所证明的),但我想知道是否也可以使用Java中支持的一切,用更高级别的表达式和语句消除所有的goto语句。
或者,如果你喜欢的话:我想要的是"重写规则",无论goto是如何创建的,它都将始终有效。
它主要是作为一个理论问题,纯粹是作为兴趣;不是一种好的/坏的做法。
我想到的显而易见的解决方案是使用这样的东西:
while (true)
{
switch (state) {
case [label]: // here's where all your goto's will be
state = [label];
continue;
default:
// here's the rest of the program.
}
}
虽然这可能会奏效,而且确实符合我的"正式"问题,但我一点也不喜欢我的解决方案。首先,它非常丑陋;其次,它基本上将goto封装成一个与goto完全相同的开关
那么,有更好的解决方案吗?
更新1
由于很多人似乎认为这个问题"过于宽泛",我将进一步阐述。。。我之所以提到Java,是因为Java没有"goto"语句。作为我的一个业余项目,我试图将C#代码转换为Java,这被证明是非常具有挑战性的(部分原因是Java中的这种限制)。
这让我思考。如果您在Open addressing中实现了"remove"方法(参见:http://en.wikipedia.org/wiki/Open_addressing-注意1),在特殊情况下使用"goto"非常方便,尽管在这种特殊情况下,您可以通过引入"state"变量来重写它。注意,这只是一个例子,我实现了用于continuations的代码生成器,当你试图反编译它们时,它会产生大量的goto。
我也不确定在这个问题上的重写是否总是会消除"goto"语句,以及它是否在所有情况下都被允许。虽然我不是在寻找一个正式的"证据",但一些证据表明在这件事上消除是可能的,那就太好了。
因此,关于"广度",我向所有认为"答案太多"或"改写goto的方法太多"的人提出挑战,请提供一种算法或改写一般情况的方法,因为到目前为止,我找到的唯一答案是我发布的答案。
这篇1994年的论文:驯服控制流:消除后藤的结构化方法Statements提出了一种算法来消除C程序中的所有goto语句。该方法适用于任何用C#编写的程序,也适用于任何使用if/switch/roop/break/continue(AFAIK,但我不明白为什么它不会)等常见结构的语言。
它从两个最简单的转换开始:
-
案例1
Stuff1(); if (cond) goto Label; Stuff2(); Label: Stuff3();
变为:
Stuff1(); if (!cond) { Stuff2(); } Stuff3();
-
案例2
Stuff1(); Label: Stuff2(); Stuff3(); if (cond) goto Label;
变为:
Stuff1(); do { Stuff2(); Stuff3(); } while (cond);
并在此基础上检查每个复杂的情况,并应用迭代转换来产生这些琐碎的情况。然后,它以最终的gotos/标签根除算法结束。
这是一本非常有趣的读物。
更新:关于这个主题的其他一些有趣的论文(不容易获得,所以我在这里复制了直接链接供参考):
删除Goto语句的正式基础
McCat C编译器的Goto消除方法及其实现
一种可以避免Goto的情况,但我认为最好使用它:
当我需要退出内部循环和循环时:
for(int i = 0; i < list.Count; i++)
{
// some code that initializes inner
for(int j = 0; j < inner.Count; j++)
{
// Some code.
if (condition) goto Finished;
}
}
Finished:
// Some more code.
为了避免goto,你应该这样做:
for(int i = 0; i < list.Count; i++)
{
// some code that initializes inner
bool conditon = false;
for(int j = 0; j < inner.Count; j++)
{
// Some code that might change condition
if (condition) break;
}
if (condition) break;
}
// Some more code.
我认为有了后藤的声明看起来好多了。
如果内部循环使用不同的方法,则第二种情况是可以的。
void OuterLoop(list)
{
for(int i = 0; i < list.Count; i++)
{
// some code that initializes inner
if (InnerLoop(inner)) break;
}
}
bool InnerLoop(inner)
{
for(int j = 0; j < inner.Count; j++)
{
// Some code that might change condition
if (condition) return true;
}
return false;
}
我有一些实践经验,尝试采用非结构化程序(COBOL),并通过删除GOTO的每个实例将其呈现为结构化程序。最初的程序员是一个经过改造的Assembly程序员,虽然他可能知道PERFORM语句,但他没有使用它。这是一个严肃的意大利面条代码——几百行的意大利面条编码。我花了几个星期的业余时间试图改写这个可怕的结构,最终我不得不放弃。这是一大堆疯狂的东西。不过效果不错!它的工作是解析以文本格式发送到大型机的用户指令,而且做得很好。
所以,不,完全消除GOTO并不总是可能的——如果你使用手动方法。然而,这是一个边缘案例——现有的代码是由一个显然有着扭曲编程头脑的人编写的。在现代,有一些工具可以解决以前难以解决的结构问题。
从那天起,我就用Modula-2、C、启示基础、VB的三种风格和C#进行了编码,从未发现需要甚至建议将GOTO作为解决方案的情况。然而,对于最初的BASIC来说,GOTO是不可避免的。
既然你说这是一个理论问题,下面是理论答案。
Java是图灵完备的,所以,是的。您可以用Java表达任何C#程序。你也可以在Minecraft红石或扫雷器中表达它。与这些替代方案相比,用Java表达它应该很容易。
然而,Patrice给出了一个明显更实用的答案,即一个可理解的转换算法。
我一点也不喜欢我的解决方案。首先,它非常丑陋;其次,它基本上将goto封装成一个与goto完全相同的开关
当你寻找一个可以取代goto的任何使用的通用模式时,你总是会得到这一点,它的作用与goto完全相同。它还能是什么?goto的不同用法应该用最匹配的语言结构来代替。这就是为什么首先存在作为切换语句和for循环的构造,以使创建这样的程序流更容易且不易出错。编译器仍然会生成goto(或跳转),但在我们会出错的地方,它会一直这样做。除此之外,我们不必阅读编译器生成的内容,但我们可以阅读(和编写)更容易理解的内容。
你会发现,大多数编译器构造的存在都是为了概括goto的特定用途,这些构造是基于之前存在的goto使用的常见模式创建的。Patrice Gahide提到的论文在某种程度上反过来说明了这一过程。如果您在代码中找到goto,那么您要么查看其中一个模式,在这种情况下,您应该用匹配的语言构造来替换它。或者您看到的是本质上非结构化的代码,在这种情况下,您应该实际构建代码(或者不考虑它)。将其更改为非结构化但没有goto
的内容只会使情况变得更糟。(顺便说一句,同样的泛化过程仍在进行,请考虑如何将foreach
添加到编译器中,以泛化for
的一个非常常见的用法…)