在做了一些关于如何突破二级循环的研究之后
while (true) { // Main Loop
for (int I = 0; I < 15; I++) { // Secondary loop
// Do Something
break; // Break main loop?
}
}
大多数人建议调用'goto'函数
看如下例子:
while (true) { // Main Loop
for (int I = 0; I < 15; I++) { // Secondary Loop
// Do Something
goto ContinueOn; // Breaks the main loop
}
}
ContinueOn:
然而;我经常听说'goto'声明是不好的做法。下图完美地说明了我的观点:
答案 0 :(得分:47)
编辑:
goto语句真的有多糟糕,为什么?
这取决于具体情况。我记不起任何时候我发现它使代码比重构更具可读性。它还取决于你个人对可读性的看法 - 有些人比其他人更不喜欢它,从其他答案可以清楚地看出。 (作为一个兴趣点,它广泛用于生成的代码 - C#5中的所有异步/等待代码都基于有效的许多getos。)
问题在于goto
倾向于使用的情况是一种无论如何重构有助于事情的情况 - 而goto
坚持使用解决方案随着代码变得越来越复杂,更难以遵循。
有没有比使用'goto'语句更有效的方法来打破主循环?
绝对。将您的方法提取到一个单独的函数中:
while (ProcessValues(...))
{
// Body left deliberately empty
}
...
private bool ProcessValues()
{
for (int i = 0; i < 15; i++)
{
// Do something
return false;
}
return true;
}
我通常更喜欢这样做而不是引入一个额外的局部变量来跟踪“我已经完成” - 当然,这会起作用。
答案 1 :(得分:33)
goto语句真的有多糟糕,为什么?
由于所有正常原因,这真的很糟糕。在不支持它们的语言中模拟标记循环时,它是完美的。
在很多情况下,用函数替换它会分散逻辑,这些逻辑应该被理解为同一个单元。这使得阅读更加困难。 没有人喜欢跟踪一些功能,这些功能在行程结束时,当你有点遗忘时,并没有真正做任何事情 你从哪里开始。
用布尔值和一堆额外的ifs和break来代替它真的很笨重,并且更难以像真正的噪音那样遵循真实意图。
在java(和javascript)中,这是完全可以接受的(标记为循环):
outer: while( true ) {
for( int i = 0; i < 15; ++i ) {
break outer;
}
}
在C#中,看起来非常接近的等价物不是:
while( true ) {
for (int I = 0; I < 15; I++) {
goto outer;
}
}
outer:;
由于goto
这个词,它具有一种心理作用,即让人们放弃所有常识,并使它们与上下文无关地链接xkcd。
有没有比使用'goto'语句更有效的方法来打破主循环?
在某些情况下没有,这就是为什么其他语言提供标记循环而C#提供goto
的原因。请注意,您的示例太简单了
解决方案看起来并不太糟糕,因为它们是根据示例量身定制的。事实上,我也可以这样说:
for (int I = 0; I < 15; I++) {
break;
}
这个怎么样:
int len = 256;
int val = 65536;
for (int i = 0; i < len; i++)
{
for (int j = 0; j < len; j++)
{
if (i + j >= 2 * val)
{
goto outer;
}
val = val / 2;
}
}
outer:;
这对您来说仍然很好看:
int len = 256;
int val = 65536;
for (int i = 0; i < len; i++)
{
if (!Inner(i, ref val, len))
{
break;
}
}
private bool Inner(int i, ref int val, int len)
{
for (int j = 0; j < len; j++)
{
if (i + j >= 2 * val)
{
return false;
}
val = val / 2;
}
return true;
}
答案 2 :(得分:31)
我将非常不同意这里的所有其他答案。您使用goto
呈现的代码没有任何问题。有一个原因 C#有一个goto
语句,它恰好适用于您描述的这些类型的场景。
goto
只是有一个负面的耻辱,因为在20世纪70年代和之前的人会编写可怕的,完全无法维护的代码,因为goto
控制流在整个地方跳跃。 C#的goto
甚至不允许在方法之间进行转换!然而,仍有这种不合理的耻辱。
在我看来,使用“现代”goto
打破内循环绝对没有错。人们提供的“替代品”总是最终变得更加复杂,更难以阅读。
通常认为方法是可重用的。为循环的内部部分创建一个完全独立的方法,只能从该位置调用,并且方法实现可能最终位于源代码中的某个远程位置,这不是一种改进。
这种避免goto
的愚蠢运动基本上等同于政治正确性,但在编程世界中。
答案 3 :(得分:7)
我同意大多数关于goto有多糟糕的答案。
我避免goto的消息是做这样的事情:
while (condition) { // Main Loop
for (int i = 0; i < 15; i++) { // Secondary loop
// Do Something
if(someOtherCondition){
condition = false // stops the main loop
break; // breaks inner loop
}
}
}
答案 4 :(得分:6)
我有时会使用&#34; goto&#34;我发现它看起来不错,就像上面的例子一样;
bool AskRetry(Exception ex)
{
return MessageBox.Show(you know here...) == DialogResult.Retry;
}
void SomeFuncOrEventInGui()
{
re:try{ SomeThing(); }
catch (Exception ex)
{
if (AskRetry(ex)) goto re;
else Mange(ex); // or throw or log.., whatever...
}
}
我知道你可以递归地做同样的事情,但谁关心它只是有效并且我使用。
答案 5 :(得分:4)
我的一位同事(他有15年以上的固件编程)并且我一直使用goto。但是,我们只用它来处理异常!!例如:
if (setsockopt(sd,...)<0){
goto setsockopt_failed;
}
...
setsockopt_failed:
close(sd);
据我所知,这是处理C中异常的最佳方法。我相信,我也适用于C#。此外,我不是唯一一个这么认为的人:Examples of good gotos in C or C++
答案 6 :(得分:1)
要在这里添加其他答案,除了打破嵌套循环之外,使用goto的一种巧妙方法是简单干净的状态机:
goto EntryState;
EntryState:
//do stuff
if (condition) goto State1;
if (otherCondition) goto State2;
goto EntryState;
State1:
//do stuff
if (condition) goto State2;
goto State1;
State2:
//do stuff
if (condition) goto State1;
goto State2;
这是一种非常清晰易读的状态机处理方法,最重要的是它是免费的性能友好! 尽管无需goto即可执行此操作,但实际上只会使代码的可读性降低。不要回避goto,在使用它之前请三思。
答案 7 :(得分:0)
就我个人而言,我喜欢将goto视为“goto导致地狱”......并且在许多方面都是如此,因为它可能导致高度无法维护的代码和非常糟糕的做法。
话虽如此,它仍然是有原因的,并且在使用时,如果没有其他可用解决方案,应该谨慎使用。 OO语言本身并不需要它(很多)。
这些天我曾经看过它的唯一一次是混淆......一个使用goto从地狱创建代码的例子,所以人们将被阻止试图理解它!
某些语言依赖于等效的关键字。例如,在x86 assember中,您有关键字,如JMP(跳转),JE(如果等于跳转)和JZ(跳转为零)。这些在汇编语言中经常需要,因为在这个级别没有OO,并且很少有其他方法可以在应用程序中移动。
AFAIK ......除非绝对必要,否则请远离它。