Answers:
语言管理词法范围的方式产生了同时包含goto
和的问题continue
。例如,
local a=0
repeat
if f() then
a=1 --change outer a
end
local a=f() -- inner a
until a==0 -- test inner a
local a
循环体内的声明掩盖了名为的外部变量a
,该局部变量的范围扩展了该until
语句的条件,因此该条件正在测试最里面的变量a
。
如果continue
存在,则必须在语义上进行限制,使其仅在条件中使用的所有变量都进入范围后才有效。这是很难记录给用户并在编译器中强制执行的条件。已经讨论了有关此问题的各种建议,包括不允许continue
使用repeat ... until
循环样式的简单答案。到目前为止,还没有足够令人信服的用例将它们包含在语言中。
解决方法通常是反转将导致continue
执行a的条件,并在该条件下收集其余的循环体。因此,以下循环
-- not valid Lua 5.1 (or 5.2)
for k,v in pairs(t) do
if isstring(k) then continue end
-- do something to t[k] when k is not a string
end
可以写
-- valid Lua 5.1 (or 5.2)
for k,v in pairs(t) do
if not isstring(k) then
-- do something to t[k] when k is not a string
end
end
除非您有一系列控制循环操作的精心挑选的剔除器,否则就足够清楚了,通常不会成为负担。
until...
。
goto
Lua 5.2 之前,Lua社区中对此进行了大量讨论。自然,goto
具有相同的问题。他们最终决定,无论为了防止运行时和/或代码生成成本而付出的代价,都值得拥有一种goto
可用于模拟continue
多级和多级的灵活性的好处break
。您必须在Lua列表档案中搜索相关线程以获取详细信息。自从他们引入以来goto
,它显然不是不可克服的。
local
是仅编译器指令-介于什么运行时指令local
和变量用法之间无关紧要-您无需在编译器中进行任何更改即可维持相同的作用域行为。是的,这可能并不那么明显,需要一些其他文档,但是再次重申,它要求在编译器中进行零更改。repeat do break end until true
我的答案中的示例已经生成了与编译器将继续使用的字节码完全相同的字节码,唯一的区别是,continue
您不需要难看的额外语法来使用它。
do{int i=0;}while (i == 0);
失败或C ++:do int i=0;while (i==0);
失败的等效项(“未在此范围内声明”)。不幸的是,现在改变现状已经太迟了。
您可以将循环体另外包装repeat until true
,然后do break end
在内部使用以继续效果。当然,如果您还打算真正break
脱离循环,则需要设置其他标志。
这将循环5次,每次打印1、2和3。
for idx = 1, 5 do
repeat
print(1)
print(2)
print(3)
do break end -- goes to next iteration of for
print(4)
print(5)
until true
end
这种构造甚至可以将JMP
Lua字节码转换为原义的一个操作码!
$ luac -l continue.lua
main <continue.lua:0,0> (22 instructions, 88 bytes at 0x23c9530)
0+ params, 6 slots, 0 upvalues, 4 locals, 6 constants, 0 functions
1 [1] LOADK 0 -1 ; 1
2 [1] LOADK 1 -2 ; 3
3 [1] LOADK 2 -1 ; 1
4 [1] FORPREP 0 16 ; to 21
5 [3] GETGLOBAL 4 -3 ; print
6 [3] LOADK 5 -1 ; 1
7 [3] CALL 4 2 1
8 [4] GETGLOBAL 4 -3 ; print
9 [4] LOADK 5 -4 ; 2
10 [4] CALL 4 2 1
11 [5] GETGLOBAL 4 -3 ; print
12 [5] LOADK 5 -2 ; 3
13 [5] CALL 4 2 1
14 [6] JMP 6 ; to 21 -- Here it is! If you remove do break end from code, result will only differ by this single line.
15 [7] GETGLOBAL 4 -3 ; print
16 [7] LOADK 5 -5 ; 4
17 [7] CALL 4 2 1
18 [8] GETGLOBAL 4 -3 ; print
19 [8] LOADK 5 -6 ; 5
20 [8] CALL 4 2 1
21 [1] FORLOOP 0 -17 ; to 5
22 [10] RETURN 0 1
luac
SO的输出!拥有当之无愧的
我们对“继续”的主要关注是(我们认为)还有其他一些控制结构与“继续”一样重要,甚至可能取代它。(例如,用标签打断(如Java中的标签,甚至使用更通用的goto。)。“继续”似乎比其他控制结构机制更特殊,除了它以更多的语言提供。(Perl实际上有两个“ continue”语句,“ next”和“ redo”。两者都很有用。)
continue
Lua,对不起” 更合理。
至于解决方法,您可以将循环的主体包装在一个函数中,并在此之前进行包装return
,例如
-- Print the odd numbers from 1 to 99
for a = 1, 99 do
(function()
if a % 2 == 0 then
return
end
print(a)
end)()
end
或者,如果您同时需要break
和continue
功能,请让本地功能执行测试,例如
local a = 1
while (function()
if a > 99 then
return false; -- break
end
if a % 2 == 0 then
return true; -- continue
end
print(a)
return true; -- continue
end)() do
a = a + 1
end
collectgarbage("count")
即使您尝试了100次,也要检查一下,然后我们再谈。这种“过早”的优化使上周每分钟重启一次的高负荷项目免于一次失败。
我以前从未使用过Lua,但是我用Google搜索了它,并提出了以下建议:
检查问题1.26。
这是常见的投诉。Lua的作者认为,continue只是许多可能的新控制流机制之一(它不能与重复/直到范围规则一起使用的事实是次要因素)。
在Lua 5.2中,有一个goto语句,可以很容易地用来完成相同的工作。
我们多次遇到这种情况,我们只是使用一个标志来模拟继续。我们也尝试避免使用goto语句。
示例:该代码打算打印从i = 1到i = 10的语句,但i = 3除外。此外,它还会打印“循环开始”,“循环结束”,“如果开始”和“如果结束”,以模拟代码中存在的其他嵌套语句。
size = 10
for i=1, size do
print("loop start")
if whatever then
print("if start")
if (i == 3) then
print("i is 3")
--continue
end
print(j)
print("if end")
end
print("loop end")
end
通过用测试标志将所有剩余的语句括起来直到循环的结束范围来实现。
size = 10
for i=1, size do
print("loop start")
local continue = false; -- initialize flag at the start of the loop
if whatever then
print("if start")
if (i == 3) then
print("i is 3")
continue = true
end
if continue==false then -- test flag
print(j)
print("if end")
end
end
if (continue==false) then -- test flag
print("loop end")
end
end
我并不是说这是最好的方法,但它对我们来说非常有效。
Lua是一种轻量级的脚本语言,希望尽可能地缩小。例如,许多一元运算(例如前/后递增)不可用
除了继续,您可以使用goto like
arr = {1,2,3,45,6,7,8}
for key,val in ipairs(arr) do
if val > 6 then
goto skip_to_next
end
# perform some calculation
::skip_to_next::
end
因为这是不必要的¹。在极少数情况下,开发人员会需要它。
A)当您有一个非常简单的循环(例如1或2线)时,您只需扭转循环条件,它仍然可读性强。
B)在编写简单的过程代码(也就是上个世纪我们如何编写代码)时,还应该应用结构化编程(又就是上个世纪我们如何编写更好的代码)
C)如果您正在编写面向对象的代码,则循环主体应包含不超过一或两个方法调用,除非它可以用一或两行表示(在这种情况下,请参阅A)
D)如果您正在编写功能代码,则只需为下一次迭代返回一个简单的尾调用即可。
唯一要使用continue
关键字的情况是,如果您想像Lua一样编写Lua的代码,事实并非如此。²
除非适用A),否则无需任何解决方法,则应进行结构化,面向对象或函数式编程。这些就是Lua所针对的范例,因此,如果您竭尽所能避免使用它们的模式,那您将与该语言作斗争。³
一些澄清:
¹Lua是一种非常简约的语言。它尝试拥有尽可能少的功能,continue
从这个意义上说,声明并不是必需的功能。
我认为罗伯托·伊鲁萨利姆斯基(Roberto Ierusalimschy)在2019年的这次采访中很好地体现了这种极简主义哲学:
最后,我们了解最终结论将无法满足大多数人的需求,并且我们不会提供所有人都想要的所有选择,因此我们什么也不做。最后,严格模式是一个合理的折衷方案。
²似乎有大量的程序员从其他语言来过Lua,因为他们尝试编写脚本的任何程序都碰巧使用了它,而且他们中的许多人似乎不想写其他任何东西,而不仅仅是他们的语言。选择,导致出现许多问题,例如“为什么Lua没有X功能?”
最受欢迎的问题是:“我来自X语言社区;您不能将X语言的功能引入Ruby吗?”或类似的东西。我对这些要求的通常回答是……“不,我不会那样做”,因为我们有不同的语言设计和不同的语言开发政策。
³有几种方法可以解决这个问题;一些用户建议使用goto
,这在大多数情况下已经足够好了,但很快就会变得很丑陋,并且会被嵌套循环完全破坏。使用goto
s还会使您有危险,每当您向其他人展示代码时,都会被抛出SICP的副本。
continue
也许是一个方便的功能,但这并没有必要。没有Lua,很多人都会很好地使用Lua,因此,除了没有对任何编程语言都不重要的纯净功能之外,没有其他理由可以使用Lua。
goto
可以用于继续执行的声明。请参阅下面的答案。