虽然使用转换很容易(如f.ex. IL所示),但我想知道是否也可以消除 all goto具有更高级别表达式和语句的语句 - 比如说 - 使用Java中支持的所有内容。
或者,如果您愿意:我正在寻找的是“重写规则”'无论创建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中的这种限制)。
这让我思考。如果你有f.ex. '删除'的实施开放式寻址中的方法(参见:http://en.wikipedia.org/wiki/Open_addressing - 注释1),让#goto'非常方便。在特殊情况下,虽然在这种特殊情况下你可以通过引入一个“状态”来重写它。变量。请注意,这只是一个例子,我已经实现了用于延续的代码生成器,当你尝试反编译它们时会产生大量的goto。
我也不确定在这件事上的重写是否会永远消除“goto”#39;声明,如果在每种情况下都允许。虽然我并不是在寻找正式的证据,但有些证据表明在这件事情上可能会有所消除。
关于“广泛性”,我挑战所有认为答案太多的人。或者'许多方法来重写goto'请提供一种算法或方法来重写一般情况,因为我找到的唯一答案是我发布的那个。
答案 0 :(得分:8)
1994年的论文:Taming Control Flow: A Structured Approach to Eliminating Goto Statements提出了一种算法来消除C程序中的所有goto语句。该方法适用于任何用C#编写的程序或任何使用常见结构的语言,如if / switch / loop / break / continue(AFAIK,但我不明白为什么不会这样)。
它从两个最简单的转换开始:
// CASE 1
Stuff1();
if (cond) goto Label;
Stuff2();
Label:
Stuff3();
// Becomes:
Stuff1);
if (!cond)
{
Stuff2();
}
Stuff3();
// CASE 2
Stuff1();
Label:
Stuff2();
Stuff3();
if (cond) goto Label;
// becomes:
Stuff1();
do
{
Stuff2();
Stuff3();
} while (cond);
并以此为基础来检查每个复杂的案例并应用导致那些微不足道的案例的迭代转换。然后用最终的gotos /标签根除算法完成。
这是一篇非常有趣的读物。
更新:关于这个主题的一些其他有趣的论文(不容易掌握,所以我在这里复制直接链接以供参考):
A Formal Basis for Removing Goto Statements
A Goto-Elimination Method And Its Implementation For The McCat C Compiler
答案 1 :(得分:2)
我有一些尝试采用非结构化程序(在COBOL中,不少)的实际经验,并通过删除GOTO的每个实例将其呈现为结构化。最初的程序员是一名经过改革的大会程序员,虽然他可能已经了解了PERFORM声明,但他并没有使用它。这是GOTO GOTO GOTO。这是严肃的意大利面条代码 - 数百行意大利面条代码。我花了几个星期的时间试图重写这个怪异的构造,最终我不得不放弃。这是一堆疯狂的疯狂。虽然它奏效了!它的工作是以文本格式解析发送到大型机的用户指令,并且它做得很好。
所以,不,如果您使用手动方法,并不总是可以完全消除GOTO。然而,这是一个边缘情况 - 现有的代码是由一个有着明显扭曲编程思想的人编写的。在现代,有一些工具可以解决以前棘手的结构问题。
从那天起,我编写了Modula-2,C,Revelation Basic,三种VB和C#,并且从未发现需要甚至建议将GOTO作为解决方案的情况。然而,对于原始的BASIC,GOTO是不可避免的。
答案 2 :(得分:2)
可以避免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.
为了避免转到你应该做这样的事情:
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.
我觉得goto语句看起来好多了。
如果内循环采用不同的方法,第二种情况就可以了。
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;
}
答案 3 :(得分:1)
既然你说这是一个理论问题,这就是理论上的答案。
Java是图灵完成的,当然,是的。您可以用Java表达任何C#程序。您也可以在Minecraft Redstone或Minesweeper中表达它。与在Java中表达它的这些替代方案相比应该很容易。
Patrice已经给出了一个明显更实际的答案,即进行转换的可理解算法。
答案 4 :(得分:1)
我不喜欢我的解决方案。对于其中一个,它很难看,而且对于两个,它基本上将goto包装成一个与goto完全相同的开关。
在寻找可以取代任何goto使用的一般模式时,你总会得到什么,这与goto完全相同。还有什么呢? goto的不同用法应该用最佳匹配语言结构替换。这就是为什么首先构造为switch语句和for循环的原因,以便更容易创建这样的程序流并且更不容易出错。编译器仍会生成goto(或跳转),但它会一直这样做,我们会搞砸。最重要的是,我们不必阅读编译器生成的内容,但我们可以阅读(并编写)更容易理解的内容。
您会发现大多数编译器构造都存在以概括goto的特定用法,这些构造基于之前存在的goto使用的常见模式进行创建。帕特里斯·加希德(Patrice Gahide)的论文提到了反向处理的一些节目。如果您在代码中找到goto,则要么查看其中一种模式,在这种情况下,您应该使用匹配的语言结构替换它。或者您正在查看基本上非结构化的代码,在这种情况下,您应该实际构建代码(或不管它)。将其更改为非结构化但没有goto
的内容只会让事情变得更糟。 (相同的泛化过程仍在继续,考虑如何将foreach
添加到编译器以概括for
的常见用法...)