我在C程序中找到了以下代码:
while (1)
{
do_something();
if (was_an_error()) break;
do_something_else();
if (was_an_error()) break;
[...]
break;
}
[cleanup code]
此处while(1)
用作“finally”的本地模拟。您也可以使用goto
s:
do_something()
if (was_an_error()) goto out;
do_something_else()
if (was_an_error()) goto out;
[...]
out:
[cleanup code]
我认为goto解决方案是一种常用的习惯用法。我已经在内核源代码中看到了几个这样的习惯用法,在Diomidis Spinellis的“代码阅读”一书中也提到过。
我的问题是:什么解决方案更好?有没有具体的理由使用while(1)
解决方案?
问题943826没有回答我的问题。
答案 0 :(得分:33)
对GOTO的看似普遍的反驳很大程度上归功于Edsger Dijkstra的信“Go To Statement Considered harmful”。
如果你决定不使用goto,比如
do {
...
while(0);
可能比while(1){...}更安全,因为它保证你不会无意中循环(如果你无意中循环,而while(1)你可能无意中无限循环)。
(ab)为此目的使用do / break / while或while / break的一个优点是,你可以保证不会跳过以上构造 - goto可以用于在同一函数中提前跳转到标签。
do / break / while等过度goto的缺点是你被限制在一个出口点(紧接在循环之后)。在某些情况下,您可能需要进行分阶段清理:例如,当您打开文件句柄时,malloc一些内存,从文件中读取...如果读取失败,则需要清理malloc。如果malloc失败,您不需要清理它,但仍需要清理文件句柄。使用goto,每个清理阶段可以有一个标签,并根据错误发生的位置跳转到正确的点。
在我看来,盲目地避免使用GOTO是因为它普遍存在仇恨,而不是根据具体情况仔细推理一个案例。我使用的经验法则是“Linux内核是否做到了?如果是这样,它就不能 坏”。用任何其他现代软件工程的好例子替换linux内核。
答案 1 :(得分:10)
将代码放入一个单独的函数中,并使用return
提前退出是另一种方法,可以轻松集成指示失败性质的返回代码。
答案 2 :(得分:7)
虽然通常不鼓励使用goto,但像你这样的一些罕见情况是最佳做法不是最好的地方。
所以,如果goto使用最清晰的代码我将使用它。使用while(true)循环来模拟goto是不自然的。你真正需要的是一个转到!
答案 3 :(得分:7)
我知道我的风格不是最酷的,但我更喜欢它,因为它不需要任何特殊的结构,简洁而且不太难理解:
error = (!error) && do_something1(); error = (!error) && do_something2(); error = (!error) && do_something3(); // Cleanup code
答案 4 :(得分:5)
为什么不使用一系列if
语句?我通常以这种方式编写它,因为我发现它比循环更清晰:
bool ok = true;
do_something();
if (was_an_error()) ok = false;
if (ok)
{
do_something_else();
if (was_an_error()) ok = false;
}
if (ok)
{
do_something_else_again();
if (was_an_error()) ok = false;
}
[...]
[Cleanup code]
此外,如果您正在努力达到严格的编码标准,可能会禁止goto
,但break
和continue
通常也是如此,因此循环不一定是解决方法
答案 5 :(得分:5)
“break”理解块范围的语义,而“goto”则忽略它。换句话说,“while-break”可以翻译成像Lisp这样的函数式语言,尾部递归,“goto”不能。
答案 6 :(得分:3)
通常情况下,GOTO被认为是坏的,但在某些只有通过GOTO进行前向跳跃的地方,它们并不差。人们避免GOTO像瘟疫一样,但经过深思熟虑的GOTO使用有时是更好的解决方案恕我直言。
答案 7 :(得分:2)
我认为goto的这种用途(用于资源管理)是可以的。
答案 8 :(得分:2)
如果因任何原因无法使用goto,请使用它
我也认为这也是宏不是邪恶的案例之一:
#define DO_ONCE for (int _once_dummy = 0; _once_dummy < 1; _once_dummy++)
答案 9 :(得分:1)
我喜欢while(1)方法。我自己用它。特别是,当循环可能通过继续重复时,例如当在这样的循环中处理一个元素时,它是在多种方法中完成的。
答案 10 :(得分:1)
永远不要使用具有永久真实条件的条件循环。由于条件始终为真,为什么要使用条件循环?
永久真实条件最直接由goto表示。
答案 11 :(得分:0)
虽然使用“goto”进行错误处理的情况相当常见,但我仍然更喜欢“while”解决方案(或“do while”)。在“goto”的情况下,编译器可以保证的东西要少得多。如果您在标签名称中输入拼写错误,则编译器无法帮助您。如果有人使用另一个goto到该块中的另一个标签,很可能不会调用清理代码。当您使用更结构化的流控制结构时,您始终可以保证在循环结束后将运行哪些代码。
答案 12 :(得分:0)
“do while”和“goto out”在这些区域有所不同:
1.local变量初始化
void foo(bool t = false)
{
if (t)
{
goto DONE;
}
int a = 10; // error : Goto bypass local variable's initialization
cout << "a=" << a << "\n";
DONE:
}
可以在do ... while(0)块中初始化就地局部变量。
void bar(bool t = false)
{
do{
if (t)
{
break;
}
int a = 10; // fine
cout << "a=" << a << "\n";
} while (0);
}
宏的2个区别。 “do while”稍微好一些。宏中的“goto DONE”并非如此。 如果退出代码更复杂,请看这样:
err = some_func(...);
if (err)
{
register_err(err, __LINE__, __FUNC__);
#if defined (_DEBUG)
do_some_debug(err)
#endif
break;
}
你一次又一次地写这个代码,你可能会把它们放到宏中。
#define QUIT_IF(err) \
if (err) \
{ \
register_err(err, __LINE__, __FUNC__); \
DO_SOME_DEBUG(err) \
break; // awful to put break in macro, but even worse to put "goto DONE" in macro. \
}
代码变成:
do
{
initial();
do
{
err = do_step1();
QUIT_IF(err);
err = do_step2();
QUIT_IF(err);
err = do_step3();
QUIT_IF(err);
....
} while (0);
if (err) { // harder for "goto DONE" to get here while still using macro.
err = do_something_else();
}
QUIT_IF(err);
.....
} while (0);
3.do ... while(0)使用相同的宏处理不同的退出级别。代码如上所示。转到...不是宏的情况,因为您需要不同级别的不同标签。
通过这样说,我不喜欢他们两个。我更喜欢使用异常方法。如果不允许异常,那么我使用“do ... while(0)”,因为整个块是缩进的,它实际上比“goto DONE”样式更容易阅读。