Goto 语句 |
|
goto
语句在 Lua 5.2.0-beta-rc1 中添加 [6] [1],并在 5.2.0-beta-rc2 中进行了改进 [7]。这是一种限制性的 goto
形式,因为它
goto
可以跳转到任何可见的标签,只要它不进入局部变量的范围。 [1]
本页的其余部分探讨了这种新构造的一些用法。
-- 5.2.0-beta-rc2 for z=1,10 do for y=1,10 do for x=1,10 do if x^2 + y^2 == z^2 then print('found a Pythagorean triple:', x, y, z) goto done end end end end ::done::
-- 5.2.0-beta-rc2 for z=1,10 do for y=1,10 do for x=1,10 do if x^2 + y^2 == z^2 then print('found a Pythagorean triple:', x, y, z) print('now trying next z...') goto zcontinue end end end ::zcontinue:: end
另请参见 ContinueProposal。
-- Lua 5.2.0-beta-rc2 for x=1,5 do ::redo:: print(x .. ' + 1 = ?') local y = tonumber(io.read'*l') if y ~= x + 1 then goto redo end end
-- Lua 5.2.0-beta-rc2 for _, x in ipairs(t) do if x % 2 == 0 then print 'list has even number' goto has end end print 'list lacks even number' ::has::
-- Lua 5.1 equivalent local has for _, x in ipairs(t) do if x % 2 == 0 then has = true break end end if has then print 'list has even number' else print 'list lacks even number' end
-- 5.2.0-beta-rc1 ::a:: print 'A' if math.random() < 0.3 then goto c end ::b:: print 'B' if math.random() < 0.5 then goto a end ::c:: print 'C' if math.random() < 0.1 then goto a else goto b end
另请参见代码生成讨论 [8]。
Lua 已经有了 ProperTailRecursion,但在假设它没有的情况下,我们可以使用 goto
模拟尾调用(就像 Lua 的一些 C 源代码那样)
-- 5.2.0-beta-rc2 - factorial with tail recursion simulated with goto's -- (warning: there's no need to do this) function fact_(n, ans) ::call:: if n == 0 then return ans else n, ans = n - 1, ans * n goto call end end print(fact_(5, 1)) --> 120
-- 5.2.0-beta-rc2 function f() if not g() then goto fail end if not h() then goto cleanup_g end if not i() then goto cleanup_h end do return true end -- need do/end? ::cleanup_h:: undo_h() ::cleanup_g:: undo_g() ::fail:: return false
如果没有计算 goto
[4],则不可能。另请参见 SwitchStatement。
使用标签名称来指示它们跳转的方向(向上或向下)可能有助于提高可读性。 [10] 在下面的示例中,通常可以理解名称 continue
和 skip
将向下跳转,而名称 redo
将向上跳转。
-- 5.2.0-beta-rc2 ::redo:: for x=1,10 do for y=1,10 do if not f(x,y) then goto continue end if not g(x,y) then goto skip end if not h(x,y) then goto redo end ::continue:: end end ::skip::
如果在同一个作用域中使用两个这样的代码块,则需要对标签名称进行区分(例如 @redo1:
和 @redo2:
)或将每个代码块包装在 do/end
块中。
以下是一些关于 goto
作用域规则的示例
::a:: goto b -- valid (forward jump) goto a -- valid (backward jump) ::b:: goto c -- invalid (jump into nested block prohibited because nested label not even visible here) goto d -- invalid (jump into nested function prohibited because nested label not even visible here) do ::c:: goto a -- valid (backward jump out of nested block) goto e -- valid (forward jump out of nested block) end (function() ::d:: goto a -- invalid (jump out of nested function) end)() do ::e:: end -- valid, but not visible outside the block; above "goto e" sees only next line ::e:: -- valid goto f -- invalid (forward jump into scope of local definition) local x ::f:: goto e -- valid (backward jump across local definition) --::e:: -- this would be invalid (duplicate label in same scope)
请注意,您可以将
do <...> --::a:: goto a -- invalid (forward jump into scope of local definition) goto b -- valid (jump out of block) <...> local x <...> ::a:: <...> --goto a ::b:: end
视为等效于
do <...> --::a:: goto a -- invalid (jump into nested block prohibited because nested label not even visible here) goto b -- valid (jump out of block) <...> do local x <...> ::a:: <...> --goto a end ::b:: end
因此,在某种程度上,禁止“跳转到局部定义范围”的规则隐含在禁止“跳转到嵌套块”的规则中(但反之则不然)。但是,5.2.0-beta-rc1 并没有完全类似地处理这两种形式之间的作用域:如果在 goto a
之前添加另一个 ::a::
,则前一种形式将生成关于重复标签的错误,而后一种形式则不会(尽管它在 rc2 中会这样做),因为嵌套的 ::a::
永远不会被 goto
在嵌套块之外看到(并且嵌套块内的任何 goto
只会看到嵌套的 ::a::
)。
在块末尾对标签的特殊处理(::b::
)允许实现循环继续构造(上面的示例),即使循环块包含在继续之后的局部变量。
goto
有时可以生成与控制结构完全相同的字节码和调试信息,除了 for
循环。
-- compare.lua -- tested 5.2.0rc1 local FS = require 'file_slurp' -- https://raw.github.com/gist/1325400/0de9b965af138f2fb3d76fc81d97a863f6f409b3/file_slurp.lua local function compile(code) FS.writefile('luac -o luac.out -', code, 'p') local binary = FS.readfile('luac.out') local text = FS.readfile('./src/luac -p -l luac.out', 'p'):gsub('0x[0-9a-fA-F]+', '(address)') return binary, text end local a, at = compile [[ local x = 1 while not(x > 1e8) do x = x + 1 end ]] local b, bt = compile [[ local x = 1 ::a:: if x > 1e8 then goto e end x = x + 1 goto a; ::e:: ]] assert(a == b) assert(at == bt) local a, at = compile [[ if x then f() else g() end ]] local b, bt = compile [[ if not x then goto a end f() goto b; ::a:: g() ::b:: ]] assert(a == b) assert(at == bt) local a, at = compile [[ local sum = 0 for i=1,1E8 do sum = sum + i end ]] local b, bt = compile [[ local sum = 0 local i=1; ::a:: if i > 1E8 then goto b end sum = sum + i; i=i+1 goto a; ::b:: ]] assert(a ~= b) -- these differ significantly and the latter is about twice as slow. assert(at ~= bt) print 'DONE'
(在早期的 5.2.0beta 版本中,当一个条件块中存在单个 goto 时,一些 JMP 是多余的 [5]).
在 5.2.0-beta-rc1 中,标签使用语法 @name:
(可选带空格,例如 @ name :
)。对重复标签名称的限制有所不同。