我一直有一个印象,如果可能的话,永远不要使用goto。
然而,在前几天阅读libavcodec(它是用C编写的)时,我惊讶地注意到它的多种用法。
在支持循环和函数的语言中使用goto是否有优势?如果有,为什么?请提供一个具体的例子,清楚地说明使用goto的理由。
我一直有一个印象,如果可能的话,永远不要使用goto。
然而,在前几天阅读libavcodec(它是用C编写的)时,我惊讶地注意到它的多种用法。
在支持循环和函数的语言中使用goto是否有优势?如果有,为什么?请提供一个具体的例子,清楚地说明使用goto的理由。
当前回答
关于goto语句,它们的合法用途,以及可以用来代替“有道德的goto语句”但也可以像goto语句一样容易被滥用的替代结构,最深思熟虑和彻底的讨论是Donald Knuth的文章“使用goto语句的结构化编程”,在1974年12月的Computing Surveys(卷6,no. 1)中。4. 第261 - 301页)。
毫不奇怪,这篇39年前的论文的某些方面已经过时了:处理能力的数量级增长使得Knuth的一些性能改进对于中等规模的问题来说并不明显,从那时起就发明了新的编程语言结构。(例如,try-catch块包含Zahn的Construct,尽管它们很少以这种方式使用。)但Knuth涵盖了争论的方方面面,在任何人再次重复这个问题之前,都应该要求阅读。
其他回答
在Perl中,使用标签从循环中“goto”—使用“last”语句,这类似于break。
这样可以更好地控制嵌套循环。
也支持传统的goto标签,但我不确定是否有太多的实例,这是实现您想要的结果的唯一方法-子例程和循环应该足以满足大多数情况。
盲目地遵循最佳实践并不是最佳实践。避免将goto语句作为流控制的主要形式是为了避免产生难以阅读的意大利面条代码。如果在正确的地方谨慎使用,它们有时可以成为表达想法的最简单、最清晰的方式。Walter Bright, Zortech c++编译器和D编程语言的创造者,经常使用它们,但很明智。即使使用了goto语句,他的代码仍然完全可读。
底线:为了避免去那里而避免去那里是毫无意义的。您真正需要避免的是生成不可读的代码。如果充满goto的代码是可读的,那么它就没有任何问题。
看看在C语言中编程时何时使用Goto:
Although the use of goto is almost always bad programming practice (surely you can find a better way of doing XYZ), there are times when it really isn't a bad choice. Some might even argue that, when it is useful, it's the best choice. Most of what I have to say about goto really only applies to C. If you're using C++, there's no sound reason to use goto in place of exceptions. In C, however, you don't have the power of an exception handling mechanism, so if you want to separate out error handling from the rest of your program logic, and you want to avoid rewriting clean up code multiple times throughout your code, then goto can be a good choice.
我是什么意思?你可能会有这样的代码:
int big_function()
{
/* do some work */
if([error])
{
/* clean up*/
return [error];
}
/* do some more work */
if([error])
{
/* clean up*/
return [error];
}
/* do some more work */
if([error])
{
/* clean up*/
return [error];
}
/* do some more work */
if([error])
{
/* clean up*/
return [error];
}
/* clean up*/
return [success];
}
This is fine until you realize that you need to change your cleanup code. Then you have to go through and make 4 changes. Now, you might decide that you can just encapsulate all of the cleanup into a single function; that's not a bad idea. But it does mean that you'll need to be careful with pointers -- if you plan to free a pointer in your cleanup function, there's no way to set it to then point to NULL unless you pass in a pointer to a pointer. In a lot of cases, you won't be using that pointer again anyway, so that may not be a major concern. On the other hand, if you add in a new pointer, file handle, or other thing that needs cleanup, then you'll need to change your cleanup function again; and then you'll need to change the arguments to that function.
通过使用goto,它将是
int big_function()
{
int ret_val = [success];
/* do some work */
if([error])
{
ret_val = [error];
goto end;
}
/* do some more work */
if([error])
{
ret_val = [error];
goto end;
}
/* do some more work */
if([error])
{
ret_val = [error];
goto end;
}
/* do some more work */
if([error])
{
ret_val = [error];
goto end;
}
end:
/* clean up*/
return ret_val;
}
这样做的好处是,您的代码可以访问执行清理所需的所有内容,并且您已经成功地减少了更改点的数量。另一个好处是你的函数从多个出口点变成了只有一个;不可能不小心从函数返回而不进行清理。
此外,由于goto仅用于跳转到单个点,因此并不是为了模拟函数调用而创建大量来回跳转的意大利面代码。相反,goto实际上有助于编写更结构化的代码。
总而言之,goto应该谨慎使用,并作为最后的手段——但它是有时间和地点的。问题不应该是“你是否必须使用它”,而应该是“它是使用它的最佳选择”。
“goto”的问题和“无goto编程”运动最重要的论点是,如果你过于频繁地使用它,尽管你的代码可能表现正确,但它会变得不可读、不可维护、不可审查等。在99.99%的情况下,“goto”会导致意大利面条代码。就我个人而言,我想不出任何好的理由来解释为什么我会使用“goto”。
我发现do{} while(false)的用法完全令人反感。它可能会让我相信它在某些奇怪的情况下是必要的,但从来没有让我相信它是干净合理的代码。
如果必须执行这样的循环,为什么不显式地依赖标志变量呢?
for (stepfailed=0 ; ! stepfailed ; /*empty*/)