我一直有一个印象,如果可能的话,永远不要使用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涵盖了争论的方方面面,在任何人再次重复这个问题之前,都应该要求阅读。
其他回答
看看在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:
例子:
// 1
try{
...
throw NoErrorException;
...
} catch (const NoErrorException& noe){
// This is the worst
}
// 2
do {
...break;
...break;
} while (false);
// 3
for(int i = 0;...) {
bool restartOuter = false;
for (int j = 0;...) {
if (...)
restartOuter = true;
if (restartOuter) {
i = -1;
}
}
etc
etc
在Perl中,使用标签从循环中“goto”—使用“last”语句,这类似于break。
这样可以更好地控制嵌套循环。
也支持传统的goto标签,但我不确定是否有太多的实例,这是实现您想要的结果的唯一方法-子例程和循环应该足以满足大多数情况。
“goto”的问题和“无goto编程”运动最重要的论点是,如果你过于频繁地使用它,尽管你的代码可能表现正确,但它会变得不可读、不可维护、不可审查等。在99.99%的情况下,“goto”会导致意大利面条代码。就我个人而言,我想不出任何好的理由来解释为什么我会使用“goto”。
goto不好的一个原因是,除了编码风格之外,你可以用它来创建重叠但非嵌套的循环:
loop1:
a
loop2:
b
if(cond1) goto loop1
c
if(cond2) goto loop2
这将创建一个奇怪的,但可能是合法的流控制结构,其中可能有(a, b, c, b, a, b, a, b, b,…)这样的序列,这让编译器黑客不高兴。显然,有许多聪明的优化技巧依赖于这种类型的结构不发生。(我应该检查一下我的龙书……)这样做的结果(使用一些编译器)可能是对包含gotos的代码没有进行其他优化。
如果你知道它只是“哦,顺便说一下”,恰好说服编译器发出更快的代码,那么它可能会很有用。就我个人而言,我更喜欢在使用像goto这样的技巧之前尝试向编译器解释什么是可能的,什么是不可能的,但可以说,我也可能在破解汇编程序之前尝试goto。