通常,避免使用GOTO是一种好习惯。牢记这一点,我一直在与同事讨论这个话题。
请考虑以下代码:
Line:
while( <> ) {
next Line if (insert logic);
}
使用循环标签计数为goto吗?
以下是perldoc中perlsyn的说法:
以下是C程序员如何在Perl中编写特定算法的代码:
for (my $i = 0; $i < @ary1; $i++) {
for (my $j = 0; $j < @ary2; $j++) {
if ($ary1[$i] > $ary2[$j]) {
last; # can't go to outer :-(
}
$ary1[$i] += $ary2[$j];
}
# this is where that last takes me
}
然而,这里的Perl程序员对这个习语的熟悉程度如何:
OUTER: for my $wid (@ary1) {
INNER: for my $jet (@ary2) {
next OUTER if $wid > $jet;
$wid += $jet;
}
}
我对此的看法并不是因为你明确地告诉了一个短路和推进的循环然而我的同事不同意,说它只是一个花哨的GOTO,应该避免。 我正在寻找一个令人信服的论点或文档,解释为什么这是或不是GOTO。我也会接受一个解释为什么这是或不被认为是perl的好习惯。< / p>
答案 0 :(得分:28)
Dijkstras的意图绝不是任何类似goto的东西都被认为是有害的。这就是使用gotos作为几乎任何类型的程序流程更改的主要构造的代码结构,这将导致我们今天称之为意大利面条代码。
你应该阅读原文,并记住它是在1968年编写的,当时标记的跳转是几乎所有编程语言中的主要流控制构造。
答案 1 :(得分:16)
GOTO标签的危险在于它们会创建意大利面条代码并使逻辑不可读。在这种情况下,这些都不会发生。使用GOTO语句有很多有效性,大部分辩护来自Donald Knuth [article]。
深入探讨C和Perl示例之间的差异......如果您考虑使用C程序在程序集级别上发生的事情,那么无论如何都会编译为GOTO。如果您已经完成了任何MIPS或其他汇编编程,那么您已经看到大多数这些语言没有任何循环结构,只有条件和无条件分支。
最终归结为可读性和可理解性。通过保持一致,这两者都得到了巨大的帮助。如果您的公司有风格指南,请遵循这一点,否则遵循perl风格指南听起来对我来说是一个好主意。这样,当其他perl开发人员将来加入您的团队时,他们将能够立即投入使用并熟悉您的代码库。
答案 2 :(得分:5)
只要能让代码更容易理解,谁会关心它是否具有goto效果?使用goto通常比在if()和循环条件下进行一系列额外测试更具可读性。
答案 3 :(得分:3)
IMO,你的代码比较是不公平的。目标是可读代码。
公平地说,你应该将一个惯用的Perl嵌套循环与没有它们的标签进行比较。 C样式for和阻止if语句添加噪声,使得无法比较这些方法。
标签:
OUTER: for my $wid (@ary1) {
INNER: for my $jet (@ary2) {
next OUTER if $wid > $jet;
$wid += $jet;
}
}
没有标签:
for my $wid (@ary1) {
for my $jet (@ary2) {
last if $wid > $jet;
$wid += $jet;
}
}
我更喜欢带标签的版本,因为它明确了条件$wid > $jet
的效果。没有标签你需要记住last
在内循环上运行,当内循环完成时,我们移动到外循环中的下一个项目。这不完全是火箭科学,但它是真实的,可证明的,认知开销。如果使用得当,标签会使代码更具可读性。
<强>更新强>
stocherilac 询问如果嵌套循环后有代码会发生什么。这取决于你是想根据内部条件跳过它还是总是执行它。
如果要跳过外部循环中的代码,标记的代码将按预期工作。
如果您想确保每次都执行它,您可以使用continue
块。
OUTER: for my $wid (@ary1) {
INNER: for my $jet (@ary2) {
next OUTER if $wid > $jet;
$wid += $jet;
}
}
continue {
# This code will execute when next OUTER is called.
}
答案 4 :(得分:2)
我认为这种区别有点模糊,但这是goto perldoc关于(皱眉)goto语句的陈述:
goto-LABEL表单找到标有LABEL的语句并在那里继续执行。
...
Perl的作者从未觉得需要使用这种形式的goto(在Perl中,也就是说; C是另一回事)。 (区别在于C不提供与循环控制相结合的命名循环.Perl确实如此,这取代了其他语言中goto的大多数结构化用法。)
然而,perlsyn perldoc说明了这一点:
只要表达式为true,while语句就会执行该块。只要表达式为false,until语句就会执行该块。 LABEL是可选的,如果存在,则由标识符后跟冒号组成。 LABEL标识循环控制语句next,last和redo的循环。如果省略LABEL,则循环控制语句引用最内层的封闭循环。这可能包括在运行时动态回顾您的调用堆栈以找到LABEL。如果您使用use warnings pragma或-w标志,这种绝望的行为会触发警告。
绝望的行为对我来说看起来不太好,但我可能会误解其含义。
Learning Perl书(第5版,第162页)有这样的说法:
当您需要使用不是最里面的循环块时,请使用标签。
...
请注意,标签命名整个块;它没有标记代码中的目标点。 [毕竟这不是转到。]
这有助于澄清事情吗?可能不是......: - )
答案 5 :(得分:2)
Perl中标记的循环跳转与C的break
和continue
一样多。
答案 6 :(得分:2)
我会这样回答,我不确定这是否与其他人所说的完全不同:
因为你只能在当前范围内或父范围内移动,所以它们比goto
通常隐含的更少危险,观察:
if (1) {
goto BAR;
die 'bar'
}
BAR:
这显然应该有效,但这不会(不能朝这个方向移动)。
if (0) {
BAR:
die 'bar'
}
goto BAR;
labels
的许多用例与goto的不同之处在于它们只是核心流量控制的更明确的变体。要声明他们明显更糟,那就是暗示:
LOOP: while (1) {
next LOOP if /foo;
}
比某种方式更糟糕
while (1) {
next if /foo/;
}
如果你排除样式,这是不合逻辑的。但是,谈到风格,后一种变体更容易阅读 - 它确实阻止你不得不查找正确命名的标签。读者对next
了解更多(您正在重新启动当前范围内的循环),这样更好。
让我们看另一个例子
while (1) {
while (1) {
last;
}
stuff;
}
-vs -
FOO: while (1) {
BAR: while (1) {
next FOO;
}
stuff;
}
在后一个示例next FOO
中,跳过stuff
- 你可能会想要这个,但这是个坏主意。这意味着程序员已经读完父范围,这是一个可能更好避免的假设。总而言之,label
并不像goto
那么糟糕,有时它们可以简化代码;但是,在大多数情况下,应该避免它们。当我在CPAN上遇到它时,我通常会在没有label
的情况下重写循环。
答案 7 :(得分:1)
gotos
很糟糕,因为它们会创建难以理解的代码 - 特别是通常称为“Spaghetti Code”的代码。关于next Line...
??
您可以将其称为循环“名称”,它确实可以帮助强调循环边界。你不会跳到与循环相关的任意点;你会回到循环的顶端。
可悲的是,如果它是一个团体或房屋标准,可能没有任何东西可以说服该团体它不是一个goto。我有一位经理绝对坚持认为三元运算符使得难以阅读,并且我更喜欢使用if-blocks来处理所有事情。我有一个非常好的论据,可以在if-else的子句中做任何事情,但是三元组明确表示你正在寻找一个特定的值。没有销售。
答案 8 :(得分:1)
这种跳跃是一种用于类似goto语句的纪律。所以它肯定比无纪律地使用goto有害。 (正如kasperjj所写的那样,“ Dijkstras的意图绝不是任何类似goto的东西都被认为是有害的。”)
IMO,这种Perl类型的跳转设计比C的“break”和“continue”更好,因为它清楚地说明了我们打破或继续的循环,并且它使代码更改时更加稳固。 (此外,它还允许打破或继续外循环。)
有些专家不喜欢休息/继续等等,但在某些时候需要在经验法则和可读性之间进行权衡,精心选择的中断/继续甚至goto可能会变得更具可读性而不是“政治正确”的代码。
答案 9 :(得分:1)
中断/最后一个并继续/下一个ARE gotos。我不明白为什么有人会花这么长时间来避免一个关键词然后使用一个不同的关键词做同样的事情......
答案 10 :(得分:1)
4.4.4。循环控制
我们提到过你可以在一个循环上放一个LABEL来给它命名。循环的LABEL标识循环控制操作符next,last和redo的循环。 LABEL将循环命名为一个整体,而不仅仅是循环的顶部。因此,引用循环的循环控制操作符实际上并不“转到”循环标签本身。就计算机而言,标签可以很容易地放在循环的末尾。但是出于某种原因,人们喜欢顶部标记的东西。
编程Perl