Lua 从入门到精通 / 04 - 控制流程 / Control Flow
控制流程 / Control Flow
Lua 的控制结构简洁明了,用 then/end、do/end 替代了花括号,用 elseif 替代了 else if。
Lua’s control structures are clean — then/end and do/end replace braces, elseif replaces else if.
🟢 基础 / Basics — 四种循环和条件
1. if / elseif / else
local score = 85
if score >= 90 then
print("优秀 / Excellent")
elseif score >= 80 then
print("良好 / Good")
elseif score >= 60 then
print("及格 / Pass")
else
print("不及格 / Fail")
end
-- 输出: 良好 / Good
注意:Lua 没有 switch,也没有三元运算符 / No switch, no ternary:
-- 模拟三元运算符(仅当结果都不是 false/nil 时安全)
local status = (score >= 60) and "Pass" or "Fail"
2. while 循环
local i = 1
while i <= 5 do
print("Count: " .. i)
i = i + 1
end
-- Count: 1
-- Count: 2
-- ...
-- Count: 5
3. repeat…until 循环(至少执行一次)
-- repeat...until 类似 do...while,但条件为真时停止
-- repeat...until is like do...while, stops when condition is true
local i = 1
repeat
print("Step: " .. i)
i = i + 1
until i > 5
-- 特点:循环体内的局部变量在 until 条件中可见!
local x = 10
repeat
local y = x * 2 -- y 在 until 中可见
print(y)
x = x - 1
until x < 1 or y > 15 -- 可以使用 y
4. 数字 for 循环
-- 基本形式:for var = start, stop, step do
-- step 默认为 1
-- 从 1 到 5
for i = 1, 5 do
print(i)
end
-- 指定步长
for i = 0, 10, 2 do
print(i) -- 0, 2, 4, 6, 8, 10
end
-- 倒序
for i = 5, 1, -1 do
print(i) -- 5, 4, 3, 2, 1
end
-- 注意:循环变量是局部的,循环结束后不可访问
print(i) -- nil
5. 泛型 for 循环(遍历器模式)
-- 遍历数组(ipairs)/ Iterate array
local fruits = {"apple", "banana", "cherry"}
for i, v in ipairs(fruits) do
print(i, v)
end
-- 1 apple
-- 2 banana
-- 3 cherry
-- 遍历表(pairs)/ Iterate table
local person = {name="Alice", age=30, city="Beijing"}
for k, v in pairs(person) do
print(k, v)
end
-- name Alice
-- age 30
-- city Beijing
-- (顺序不确定!)
6. break 和 goto
-- break 跳出当前循环
for i = 1, 100 do
if i > 5 then
break
end
print(i)
end
-- goto(Lua 5.2+)跳转到标签
for i = 1, 10 do
if i == 5 then
goto continue
end
print(i)
::continue:: -- 标签定义
end
-- 输出 1,2,3,4,6,7,8,9,10(跳过了 5)
🟡 进阶 / Intermediate — 实用模式
1. 遍历的陷阱:pairs vs ipairs
local t = {10, 20, 30, nil, 50}
-- ipairs 从 1 开始,遇到 nil 停止
for i, v in ipairs(t) do
print(i, v)
end
-- 1 10
-- 2 20
-- 3 30
-- (遇到 t[4]=nil 就停了!)
-- pairs 遍历所有键值对,但顺序不确定
for k, v in pairs(t) do
print(k, v)
end
-- 会遍历 1,2,3,5(跳过 nil,但可能乱序)
2. for 循环中的坑 / Pitfalls in for Loops
-- 坑 1:for 循环的三个表达式只计算一次
local n = 5
for i = 1, n do
print(i)
n = 10 -- 修改 n 不会影响循环次数!
end
-- 只循环 5 次,因为 n 的值在循环开始时就确定了
-- 坑 2:循环变量不能修改(修改无效)
for i = 1, 5 do
i = i + 100 -- 无效,下一次迭代 i 仍按步长递增
print(i)
end
-- 坑 3:浮点数循环可能不精确
for i = 0, 1, 0.1 do
print(string.format("%.1f", i))
end
-- 可能不会精确地在 1.0 停止(浮点误差)
-- 安全做法:用整数循环
for i = 0, 10 do
print(i / 10)
end
3. while 的实用模式 / Useful while Patterns
-- 模式一:读取直到结束
local function readLines(filename)
local lines = {}
local file = io.open(filename, "r")
if not file then return nil end
while true do
local line = file:read("*l")
if not line then break end -- 到达文件末尾
table.insert(lines, line)
end
file:close()
return lines
end
-- 模式二:指数退避
local function retry(fn, maxRetries)
local delay = 1
local retries = 0
while retries < maxRetries do
local ok, result = pcall(fn)
if ok then return result end
retries = retries + 1
print("Retry " .. retries .. " after " .. delay .. "s")
os.execute("sleep " .. delay)
delay = delay * 2 -- 指数退避
end
error("Max retries exceeded")
end
4. goto 的高级用法 / Advanced goto Usage
-- 用途 1:跳出多层循环(Lua 没有 break n)
for i = 1, 10 do
for j = 1, 10 do
if i * j > 50 then
goto done
end
print(i, j)
end
end
::done::
print("Exited nested loops")
-- 用途 2:继续外层循环(模拟 continue in nested loop)
for i = 1, 5 do
for j = 1, 5 do
if j == 3 then goto next_j end
print(i, j)
::next_j::
end
end
-- 用途 3:状态机
local function parse(str)
local i = 1
::start::
if i > #str then goto done end
local c = str:sub(i, i)
if c == " " then
i = i + 1
goto start
end
print("Char: " .. c)
i = i + 1
goto start
::done::
end
🔴 高级 / Advanced — 控制流的字节码
1. if 语句的字节码
-- 源码 / Source:
local x = 10
if x > 5 then
print("big")
else
print("small")
end
-- 字节码 / Bytecode:
-- 1 LOADK 0 0 ; R(0) = 10
-- 2 LE 0 0 1 ; if not (R(0) > 5) then PC++
-- 3 JMP 0 3 ; goto "big" code
-- 4 GETGLOBAL 1 1 ; R(1) = print
-- 5 LOADK 2 2 ; R(2) = "small"
-- 6 CALL 1 2 1 ; print("small")
-- 7 JMP 0 3 ; skip "big" code
-- 8 GETGLOBAL 1 1 ; R(1) = print
-- 9 LOADK 2 3 ; R(2) = "big"
-- 10 CALL 1 2 1 ; print("big")
2. 数字 for 的字节码
-- 源码 / Source:
for i = 1, 5 do print(i) end
-- 字节码 / Bytecode:
-- 1 LOADK 0 0 ; limit = 5
-- 2 LOADK 1 1 ; step = 1
-- 3 LOADK 2 2 ; R(2) = 1 (initial)
-- 4 FORPREP 0 3 ; prepare loop (R(0) -= step)
-- 5 GETGLOBAL 3 3 ; R(3) = print
-- 6 MOVE 4 2 ; R(4) = i
-- 7 CALL 3 2 1 ; print(i)
-- 8 FORLOOP 0 -4 ; i += step; if i <= limit then goto 5
3. 泛型 for 的字节码
-- 源码 / Source:
for k, v in pairs(t) do print(k, v) end
-- 字节码简化解释:
-- 1. 调用 pairs(t) 获取迭代器函数、状态、初始值
-- 2. TFORCALL: 调用迭代器函数获取下一个 k, v
-- 3. TFORLOOP: 如果 k ~= nil,跳回循环体
-- 4. 执行循环体
-- 泛型 for 的完整展开等价于:
local _f, _s, _var = pairs(t)
while true do
local k, v = _f(_s, _var)
_var = k
if k == nil then break end
print(k, v)
end
小结 / Summary
| 层级 | 你需要知道的 / What You Need to Know |
|---|---|
| 🟢 基础 | if/elseif/end、while/do/end、repeat/until、for/ ipairs/pairs、break |
| 🟡 进阶 | pairs vs ipairs 陷阱、浮点循环精度、goto 跳出多层循环、模拟 continue |
| 🔴 高级 | if 的 JMP 字节码、for 的 FORPREP/FORLOOP、泛型 for 的迭代器展开 |
下一章:函数 / Functions