🔥 本文专栏:Lua
🌸作者主页:努力努力再努力wz



💪 今日博客励志语录:
真正强大的人,不是永远充满信心,而是在信心不足时,依然愿意相信下一次尝试。
思维导图:本篇文章的学习逻辑
text
Lua 源代码
↓
编译为 Lua 字节码
↓
虚拟机解释执行字节码
↓
程序有一个起始执行位置,但不总是简单地线性向下执行
↓
控制结构:选择、重复、跳转
│
├── 条件分支:if / elseif / else
│ └── then 与 end、真假判断、嵌套 if
│
├── 循环:while / repeat ... until / 数值 for
│ ├── 先判断与后判断
│ └── 循环控制变量、局部变量遮蔽
│
├── 流程跳转:break / goto / return
│ ├── 退出循环、跳转标签、结束函数
│ └── goto 与局部变量的作用域限制
│
└── 独立代码块:do ... end
└── 词法作用域、同名局部变量与外部变量

一、为什么我们需要控制结构?从 Lua 的执行机制说起
在前面的 Lua 学习过程中,我们已经建立了一个基本认知:CPU 不能直接将我们写的 Lua 源码当成机器指令执行。
对于标准 Lua 的执行模型,可以将这个过程理解为两个主要阶段:
- 编译阶段:将 Lua 源代码编译成 Lua 虚拟机能够识别的字节码,也就是一套虚拟指令。
- 解释执行阶段:虚拟机根据字节码中的指令执行相应操作;CPU 最终执行的仍然是解释器中早已编译好的原生机器代码。
这里我们之前已经认识过,虚拟机核心的工作可以概括为 取指、分派、执行。它遇到不同的字节码指令,就进入对应的处理逻辑。
那么,一段 Lua 脚本从哪里开始执行?
lua
local a = 10
local b = 20
print(a)
print(b)
Lua 脚本并不要求我们像 C/C++ 那样自己定义 main()。对于这样一个脚本,程序从顶层的第一条可执行语句开始,正常情况下依次执行后面的语句。
但是,真实程序不可能永远只做这种简单的线性执行。例如,我们可能希望:
- 只有条件满足时,才执行某一段代码;
- 某段代码需要反复执行,而不是只执行一次;
- 执行到特定情况时,提前结束循环或函数;
- 直接跳转到某个指定的位置,继续执行。
这就引出了控制结构(Control Structures)。
控制结构的核心作用,就是根据程序逻辑改变正常的执行路径。 它并没有改变 Lua 源码需要经过编译与解释执行这一事实,而是让程序执行到相关结构时,可以选择某条分支、重复某段代码,或者跳过一部分指令。
从虚拟机的角度看,条件分支与循环通常会被编译成包含条件判断和跳转的字节码:条件成立时沿一条路径执行,条件不成立时沿另一条路径执行,循环则会根据需要跳回先前的位置。
这里需要区分一下:源码的书写顺序、编译处理过程,以及运行时真正执行的路径,是不同的概念。 我们下面讨论的控制结构,主要关注的是第三个:运行时的执行流程。
二、if 条件分支:重点是 Lua 的语法与 end 配对
2.1 基本语法及真假判断
先来看一个最基本的条件分支:
lua
local score = 85
if score >= 90 then
print("优秀")
elseif score >= 60 then
print("及格")
else
print("不及格")
end
这里和 C/C++ 中的 if / else if / else 没有本质的执行逻辑差别,但是需要注意 Lua 自己的语法:
if后面是条件表达式,随后必须有then;- Lua 使用的是一个关键字
elseif,而不是 C/C++ 的else if; else表示前面的条件都没有成立时执行的分支;- 整个
if ... elseif ... else结构,最后只需要一个end。
程序按顺序判断条件。一旦某个分支成立,就执行该分支的代码,然后离开整个 if 结构,不再检查后面的分支。如果没有任何分支可执行,就直接继续执行 end 后面的代码。
这里还有一条很重要的规则:
Lua 只有 nil 和 false 被视为假,其余所有值都被视为真。
lua
if 0 then
print("0 也是真")
end
if "" then
print("空字符串也是真")
end
if nil then
print("这里不会执行")
end
因此,Lua 的 if 后面不要求一定是 boolean 类型的表达式。这个真假判断规则同样适用于后面将要学习的循环条件。
2.2 为什么每个 if 都必须有对应的 end?
这部分比较值得结合 C/C++ 来理解。
对于 C/C++,我们通常通过 {} 来划分代码块:
cpp
if (a > 10) {
printf("a > 10\n");
} else if (a > 5) {
printf("a > 5\n");
} else {
printf("other\n");
}
而 Lua 使用的是 then、elseif、else、end 这些关键字共同描述结构边界:
lua
if a > 10 then
print("a > 10")
elseif a > 5 then
print("a > 5")
else
print("other")
end
这里的 end 结束的是完整的 if 语法结构,而不是要求每一个分支都分别写一个 end。
为什么我们要特别关注这个问题?因为它还关联到 C/C++ 中一个经典的现象:悬空 else(Dangling Else)。
例如:
cpp
if (a > 10)
if (b > 10)
printf("Hello\n");
else
printf("World\n");
printf("End\n");
由于这里省略了 {},else 会与最近的、尚未匹配 else 的 if 关联。因此:
else属于内层的if (b > 10);- 外层的
if (a > 10)没有else; - 如果外层条件不成立,整个内层结构都会被跳过,直接执行后面的
printf("End")。
Lua 中同样可以写嵌套 if,但是每一个 if 都必须有自己对应的 end:
lua
if a > 10 then -- 外层 if
if b > 10 then -- 内层 if
print("Hello")
else
print("World") -- 这个 else 属于内层 if
end -- 结束内层 if
end -- 结束外层 if
print("End")
如果我们想让 else 属于外层,那么必须先结束内层:
lua
if a > 10 then
if b > 10 then
print("Hello")
end -- 先结束内层
else -- 这里才是外层的 else
print("World")
end -- 结束外层

如果嵌套了两个 if,却只写一个 end:
lua
if a > 10 then
if b > 10 then
print("Hello")
else
print("World")
end
编译器会发现其中一个 if 结构没有结束,因此在编译阶段报语法错误。
这很像我们在 C/C++ 条件编译中见到的 #if ... #endif 配对形式。但请注意,两者的用途并不相同:C/C++ 的 #if 是预处理条件编译,而 Lua 的 if 是运行时条件判断。 我们只是在"开始标记与结束标记必须配对"这个形式上做类比。
本节结论 :Lua 的
if负责条件判断,then引出分支,elseif / else组织后续分支,而end结束完整的if结构。嵌套多少个独立if,就需要多少个对应的end。
三、循环结构:从执行顺序理解 while 与 repeat ... until
前面我们通过 if 解决了"是否执行某段代码"的问题。但是如果一段代码要重复执行,就需要循环。
3.1 while:先判断条件,再执行循环体
lua
local a = 1
while a <= 3 do
print(a)
a = a + 1
end
print("循环结束")
输出:
text
1
2
3
循环结束
其语法可以概括为:
lua
while 条件表达式 do
-- 循环体
end
while 与 do 之间是条件表达式,do 与 end 之间是循环体。
这里的执行过程很直观:
- 先判断
while后面的条件; - 条件为真,执行一次循环体;
- 循环体执行完成后,再回到
while重新判断; - 条件为假,就退出循环,继续执行
end后面的代码。
因此,while 可能一次循环体都不执行。它使用的真假判断规则仍然是:只有 nil 和 false 为假,其余值都为真。
另外,Lua 没有 C/C++ 中的 ++ 运算符,因此我们通常写成 a = a + 1。
3.2 repeat ... until:先执行,再判断是否退出
Lua 还提供另一种循环:
lua
local a = 1
repeat
print(a)
a = a + 1
until a > 3
print("循环结束")
同样输出 1、2、3 和 循环结束。
这里有两个需要注意的地方。
第一,repeat ... until 不需要额外的 end。 repeat 标记循环开始,until 后面则写结束条件。
第二,until 条件为真时,是退出循环,而不是继续循环。
这可以直接对比 C/C++ 的 do ... while:
cpp
do {
// 循环体
} while (condition); // 条件为真,继续循环
lua
repeat
-- 循环体
until condition -- 条件为真,退出循环
因此,我们可以这样记忆:
while:满足条件,就继续循环;repeat ... until:一直循环,直到满足条件才退出。
repeat 先执行循环体后判断,所以循环体至少执行一次;while 则可能一次都不执行。

3.3 一个容易忽略的区别:until 能访问循环体内声明的局部变量
这里就要联系我们之前学习过的 local 与作用域。
lua
repeat
local a = 10
print(a)
until a > 5
这段代码合法,而且只会执行一次循环。原因是:repeat ... until 的局部作用域包含 until 后面的条件表达式 ,所以 until a > 5 能够访问循环体中声明的局部变量 a。
而 while 不一样:
lua
local a = 10
while a > 5 do
local a = 20
print(a)
break
end
这里 while a > 5 中的 a 是外部局部变量,循环体中的 local a = 20 是新声明的内部局部变量。
关键认知 :
while后面的条件表达式,不在其循环体内部新声明的局部变量的作用域之中;而repeat ... until的until条件能够访问循环体内已经声明、且在该位置可见的局部变量。
这会引出一个更值得单独讨论的问题:相同名字的局部变量,究竟访问的是哪一个?
四、循环与词法作用域:为什么同名变量会造成死循环?
4.1 内部可以定义和外部同名的局部变量
Lua 使用词法作用域(Lexical Scope)管理局部变量。简单来说,一个局部变量在源码中有自己可见的范围;程序在某个位置访问变量名时,会根据该位置的作用域关系找到对应的变量。
例如:
lua
local a = 10
while a > 5 do
local a = 20
print(a) -- 20
break
end
print(a) -- 10
这里有两个名称相同、但实际上不同的局部变量:
- 外部
local a = 10; - 循环体内部
local a = 20。
进入内部变量的可见范围后,内部的 a 会**遮蔽(Shadowing)**外部的同名变量;离开内部变量的作用域后,再访问 a 就会找到外部的变量。
如果外部 a 是全局变量,同样可以被内部 local a 遮蔽:
lua
a = 10 -- 假设默认全局环境
while a > 5 do
local a = 20
print(a) -- 20
print(_G.a) -- 10
break
end
print(a) -- 10
需要注意:Lua 甚至允许在同一代码块内连续声明同名局部变量,后一个声明会引入新变量并遮蔽前一个,而不是像 C++ 的同一作用域重复定义那样直接报错。
lua
local a = 10
local a = 20
print(a) -- 20
4.2 结合 while 分析一个典型死循环
lua
local a = 10
while a > 5 do
local a = 20
a = a - 1
end
为什么这段代码会死循环?
我们按照执行过程一步一步来看:
- 首先创建外部局部变量
a = 10; - 执行
while a > 5,此处访问的是外部a,所以条件成立; - 进入循环体,执行
local a = 20,声明新的内部a; - 执行
a = a - 1,修改的只是内部a,由20变成19; - 循环体结束,再次回到
while a > 5,访问的仍然是外部a = 10; - 下一轮循环重新执行
local a = 20,又重复相同的过程。
因此,这段代码不会改变 while 条件所读取的外部变量,条件始终成立。

如果不重新声明内部 a,情况就完全不同:
lua
local a = 10
while a > 5 do
a = a - 1
end
print(a) -- 5
因为没有新的同名局部变量,这里修改的就是外部 a,循环会正常结束。
所以,真正需要建立的认知不是"循环体能不能使用外部变量",而是:当前代码位置到底能看见哪些局部变量,哪个同名变量处于更内层的可见范围。
五、数值 for:Lua 自动管理循环变量
掌握了条件循环以后,我们再来看 Lua 的数值 for。这部分和 C/C++ 最大的区别,是 Lua 把循环控制规则收敛成了起始值、终止值、步长三个表达式。
5.1 语法:初始值、终止值、步长
lua
for i = 1, 10, 2 do
print(i)
end
输出:
text
1
3
5
7
9
语法可以理解为:
lua
for 循环变量 = 初始值, 终止值, 步长 do
-- 循环体
end
这里的循环变量名不是固定的 i ,完全可以使用 num、index、count 等合法名称:
lua
for num = 1, 3 do
print(num)
end
步长不写时默认是 1;步长为负数时可以倒序遍历:
lua
for i = 5, 1, -1 do
print(i) -- 5 4 3 2 1(分行打印)
end
需要注意:终止值是包含式边界 ,但只有沿着给定步长恰好走到它,才会打印它。例如 1, 10, 2 不会输出 10,因为序列为 1、3、5、7、9。
对比 C/C++:
cpp
for (int i = 1; i <= 5; ++i) {
printf("%d\n", i);
}
Lua 数值 for 不需要自己编写更新表达式,它自动按照当前值 + 步长推进。
因此,它适合等差变化的循环。如果希望每次乘以 2,数值 for 不能直接把更新规则写成 i *= 2,可以使用 while:
lua
local i = 1
while i <= 16 do
print(i)
i = i * 2
end
5.2 初始值、终止值和步长,只在循环开始时计算一次
这点和常规 C/C++ for 很不一样。
lua
local limit = 3
local step = 1
for i = 1, limit, step do
print(i)
limit = 10
step = 2
end
实际输出仍然是:
text
1
2
3
为什么?因为 Lua 在进入循环之前,已经对三个控制表达式各求值一次,得到了这次循环要使用的起点、终点和步长。
后面即使修改 limit 和 step 两个外部变量,也不会改变已经确定的循环控制参数。

5.3 数值 for 循环变量的作用域与变量遮蔽
这里还有一个需要注意的细节:Lua 数值 for 中的循环控制变量,是由 for 自动声明的局部变量,它具有自己的作用域。
我们之前学习过 Lua 的 local 局部变量以及变量遮蔽机制,知道内部作用域允许声明与外部作用域同名的局部变量,此时内部变量会遮蔽外部变量。
而对于数值 for 来说,也是类似的道理。
例如:
lua
local i = 100
for i = 1, 3 do
print(i)
end
print(i)
执行结果:
text
1
2
3
100
这里我们需要注意,虽然外部已经声明了一个局部变量 i = 100,但是执行到 for i = 1, 3 do 时,Lua 会为这个数值循环创建一个新的局部控制变量 i。
也就是说,这里实际上存在两个不同的局部变量 i,只不过它们的名字相同。
其中:
- 外部的
i:初始值为100,属于外部作用域。 - 循环控制变量
i:初始值为1,由数值for自动管理,在循环体内遮蔽外部的同名变量。
因此,当程序进入循环体执行 print(i) 时,访问的是循环控制变量 i,其值依次为 1、2、3。
当循环结束之后,循环控制变量的作用域也随之结束,此时再执行 print(i),访问的就是外部原本的局部变量 i = 100。
这里需要建立的关键认知就是:变量遮蔽只决定当前代码位置访问的是哪个变量,并不意味着内部变量会继承外部变量的值。
那么,如果我们希望数值 for 使用外部变量作为初始值呢?
当然也是可以的:
lua
local i = 100
for i = i, 103 do
print(i)
end
print(i)
执行结果:
text
100
101
102
103
100
这里我们可以从两个阶段来理解:
首先,计算数值 for 的控制表达式。
对于 for i = i, 103 do,等号右侧的 i 访问的是外部局部变量 i = 100,因此初始值为 100,终止值为 103。
然后,进入循环执行阶段。
Lua 使用前面计算得到的初始值创建并管理新的循环控制变量 i,因此循环体内访问的是新的局部变量,依次输出 100、101、102、103。
循环结束后,外部的局部变量 i 并没有发生改变,所以最后仍然输出 100。
因此,我们可以总结为:
Lua 数值 for 的初始值、终止值以及步长表达式,可以访问外部作用域中的变量,而 for 自动声明的循环控制变量具有独立的局部作用域,会在循环体内遮蔽外部的同名变量。
同时,循环控制变量由 Lua 自动管理,我们不能通过直接给它赋值来改变数值 for 的循环进程。
这就和我们之前学习的 Lua 局部变量遮蔽机制联系起来了。
不过我们还需要更精确一点:三个控制表达式能够访问外部变量;新循环变量的作用域是循环体,而不包括这些控制表达式本身。
例如:
lua
local i = 10
local limit = 12
for i = i, limit do
print(i)
end
print(i) -- 10
这里等号右边的 i 读取的是外部 i = 10,limit 读取的是外部 12;而循环体中使用的是 for 引入的新变量 i。
另外,关于循环体里能否写 i = i + 2,这里要区分语法是否允许 与是否应该这样做:
你也可以在循环体内部再声明一个同名局部变量,但它只会产生遮蔽:
lua
for i = 1, 3 do
local i = 100
print(i) -- 每轮输出 100
end
本节结论 :Lua 数值
for由起点、终点和步长决定循环过程;三个控制表达式只在开始时求值一次;循环变量具有自己的局部作用域,由 Lua 自动控制。泛型for和迭代器另文讨论。
六、改变执行路径:break、goto 与 return
前面的 if 和循环,已经能够实现大部分流程控制。但有时我们需要主动提前结束或者跳转到另一个位置,这就涉及 break、goto 和 return。
6.1 break:退出最内层循环
lua
for i = 1, 10 do
if i == 5 then
break
end
print(i)
end
print("End")
这里输出 1、2、3、4,随后输出 End。
break 的作用是:直接结束当前所在的最内层循环 ,继续执行该循环之后的语句。即使 break 写在 if 内部,它结束的也是循环,而不是仅仅结束 if。
lua
for i = 1, 3 do
for j = 1, 3 do
if j == 2 then
break
end
print(i, j)
end
end
内层循环遇到 j == 2 就退出,但外层循环仍然继续。最终输出的是 (1,1)、(2,1)、(3,1)。
Lua 没有内置 continue 关键字。通常可以重新组织条件分支来跳过一轮中的某些代码;下面学习 goto 时,我们也会看到一种实现类似效果的方式。
6.2 goto:跳转到标签所在位置
goto 的语法也比较直观:
lua
goto label
::label::
其中,label 是自定义标签名,标签的语法为前后各两个冒号:::名称::。
例如:
lua
local a = 10
goto skip
print("这句不会执行")
::skip::
print(a) -- 10
从运行机制来说,goto 会改变后续代码的执行位置。
不过,Lua 的 goto 不是想跳到哪里就能跳到哪里。这里有一条非常重要的限制:
不能从某个局部变量的作用域之外,通过 goto 直接跳转进入该局部变量的作用域。
来看一个会编译失败的例子:
lua
goto skip
local a = 10
::skip::
print(a)
为什么这里会报错?我们可以结合之前的心智模型来理解:
- 从词法作用域来看 :
::skip::后面的位置已经位于局部变量a的可见范围内; - 从执行流程来看 :如果允许这样跳转,就绕过了
local a = 10的声明与初始化语句; - 产生的问题 :程序将进入一个在词法上应当存在局部变量
a、但对应声明过程却被跳过的位置; - Lua 的处理方式:编译器提前禁止这样的跳转。
这里最关键的是:报错并不是由 print(a) 引起的。 即使后面不读取 a,只要 goto 进入了这个局部变量的作用域,就违反规则。
但是,并不是所有"跳过局部变量声明"的情况都不允许:
lua
goto skip
do
local a = 10
print(a)
end
::skip::
print("Hello")
这段代码合法。因为 a 的作用域仅在 do ... end 内部,标签 ::skip:: 位于其作用域之外。跳转不会进入 a 的作用域。

因此,应该记住的是:限制不在于能否跳过某一行 local 声明,而在于跳转目标位置是否进入了局部变量的作用域。
另外,goto 不能跨函数边界跳转。它是从 Lua 5.2 开始引入的,Lua 5.1 不支持 goto。
利用 goto,也可以在循环中模拟跳过当前一轮剩余语句的效果:
lua
for i = 1, 5 do
if i == 3 then
goto continue
end
print(i)
::continue::
end
输出 1、2、4、5。这里相当于当 i == 3 时跳转到循环体末尾,然后继续下一轮循环。
6.3 return:结束当前函数,支持多返回值
return 我们在学习函数时已经接触过了,它主要负责结束当前函数的执行,并可以返回结果。
lua
function add(a, b)
local result = a + b
return result
end
print(add(10, 20)) -- 30
对于 Lua,有两个需要单独记住的特点。
第一,Lua 函数支持直接返回多个值:
lua
function test()
return 10, 20, 30
end
local a, b, c = test()
print(a, b, c) -- 10 20 30
第二,return 在语法上必须位于它所在代码块的最后一条语句。
例如,下面这段代码不合法:
lua
function test()
return 10
print("Hello") -- 语法错误:return 后仍有同一代码块中的普通语句
end
但是下面这段合法:
lua
function test(a)
if a > 10 then
return 100
end
print("继续执行")
return 200
end
因为第一个 return 是 if 分支代码块的末尾语句,第二个 return 是函数体代码块的末尾语句。
脚本顶层也可以使用 return 来结束当前 Lua 脚本编译单元的执行;不需要为此自己定义 main()。
到这里,可以先把三种跳转语句作一个区分:
| 控制语句 | 改变了什么 | 需要注意的限制 |
|---|---|---|
break |
结束最内层循环 | 只能用于循环 |
goto |
跳转到指定标签 | 不能跳入局部变量的作用域,不能跨函数跳转 |
return |
结束当前函数或脚本编译单元 | 必须位于所在代码块的最后一条语句,可返回多个值 |
七、do ... end:不需要条件,也可以创建独立代码块
经过前面的讨论,我们已经多次看到 do 和 end。例如 while ... do ... end、for ... do ... end。
但是 Lua 还有一种单独的写法:
lua
do
-- 代码块
end
它不是循环,也不是条件判断,而是显式创建一个独立的代码块。
lua
print("开始")
do
print("执行代码块")
end
print("结束")
上面的代码会正常依次打印三个结果。单独的 do ... end 不会自动产生循环,也不需要判断任何条件。
7.1 为什么要单独创建代码块?
最直接的用途就是:限制局部变量的可见范围。
这和 C/C++ 使用单独的 {} 代码块很相似。
cpp
int a = 10;
{
int a = 20;
printf("%d\n", a); // 20
}
printf("%d\n", a); // 10
Lua 中可以写成:
lua
local a = 10
do
local a = 20
print(a) -- 20
end
print(a) -- 10
内部 local a = 20 遮蔽外部的 a,但作用域只覆盖这个代码块中从声明开始的可见范围。代码块结束以后,再次访问 a 就找到外部变量。
7.2 如果内部没有重新声明 local 呢?
lua
local a = 10
do
a = 20
end
print(a) -- 20
这里没有声明新的局部变量,内部的 a = 20 修改的就是外部变量。
因此,do ... end 不是把内部代码与外部代码完全隔离;它只是创建一个新的词法作用域。内部仍然可以访问和修改在该位置可见的外部变量。
7.3 do ... end 还可以配合 return 使用
前面我们说过,Lua 规定 return 必须位于当前代码块的最后一条语句。
那么,在需要提前返回的地方,可以显式引入一个代码块:
lua
function test()
do
return 10
end
print("不会执行")
end
这是合法的,因为 return 位于内部 do ... end 代码块的最后。
需要特别注意:return 结束的是整个 test() 函数,而不只是退出 do ... end 代码块。 所以后面的 print() 不会执行。
本节结论 :
do ... end的作用是创建独立代码块,最核心的价值是管理局部变量的词法作用域。它本身不引入判断或者循环。
八、总结:把 Lua 控制结构放回统一的执行模型
现在回头来看,我们会发现这次学习的内容并不是一堆孤立的语法。
我们一开始认识 Lua 的编译与解释执行过程 ,知道虚拟机正常执行字节码,但可以通过条件与跳转改变后续执行位置;接着学习 if、while、repeat ... until 和数值 for,理解如何选择分支、如何重复执行 ;最后通过 break、goto、return 和 do ... end,把执行流程与词法作用域联系了起来。
可以将本篇的核心规则整理成下面这张表:
| 知识点 | 核心执行规则 | 最值得注意的 Lua 特性 |
|---|---|---|
if ... then ... end |
根据条件选择分支 | elseif 连写;每个 if 必须有自己的 end |
| 真值判断 | 条件表达式决定是否执行 | 只有 false 和 nil 为假,0 和 "" 为真 |
while ... do ... end |
先判断,为真才进入循环体 | 条件不在循环体内新声明局部变量的作用域中 |
repeat ... until |
先执行,为真时退出 | 不需要额外 end;until 可以访问循环体中可见的局部变量 |
数值 for |
按起点、终点、步长循环 | 三个控制表达式开始时只求值一次;循环变量独立且不应手动修改 |
break |
退出最内层循环 | 不会把所有嵌套循环一起退出 |
goto |
跳转到指定标签 | 不能跳入局部变量作用域;Lua 5.1 不支持 |
return |
结束函数并返回结果 | 支持多返回值;必须位于所在代码块最后 |
do ... end |
顺序执行一段独立代码块 | 主要用于控制局部变量的可见范围 |
最终我们要建立的心智模型是:程序的执行流程由控制结构决定,而在某个执行位置能够访问哪个变量,则由词法作用域决定。
这两个概念需要结合起来看,但又不能混为一谈:
- 条件分支、循环和跳转,解决的是下一步执行哪一段代码;
local、代码块和变量遮蔽,解决的是在当前源码位置,变量名对应哪一个变量;goto的作用域限制,则是这两套规则相互约束的典型例子。
掌握这一层关系以后,我们再阅读 Lua 项目里的控制结构,就不只是记住 then、do、end 的用法,而是能够看懂程序如何执行,以及为什么某个变量会在此处被访问或修改。
至于 Lua 的泛型 for ,我们先放在这里。它涉及迭代器,等后续把迭代器的执行机制梳理清楚,再回来讨论 pairs()、ipairs() 与泛型 for 的关系,会更符合循序渐进的学习顺序。
