3.3 -- 语句
Lua 支持所有与 Pascal 或是 C 类似的常见形式的语句, 这个集合包括赋值,控制结构,函数调用,还有变量声明。
3.3.1 – 语句块
语句块是一个语句序列,它们会按次序执行:
block ::= {stat}
Lua 支持 空语句, 你可以用分号分割语句,也可以以分号开始一个语句块, 或是连着写两个分号:
stat ::= ‘;’
函数调用和赋值语句都可能以一个小括号打头, 这可能让 Lua 的语法产生歧义。 我们来看看下面的代码片断:
a = b + c
(print or io.write)('done')
从语法上说,可能有两种解释方式:
a = b + c(print or io.write)('done')
a = b + c; (print or io.write)('done')
当前的解析器总是用第一种结构来解析, 它会将开括号看成函数调用的参数传递开始处。 为了避免这种二义性, 在一条语句以小括号开头时,前面放一个分号是个好习惯:
;(print or io.write)('done')
一个语句块可以被显式的定界为单条语句:
stat ::= do block end
显式的对一个块定界通常用来控制内部变量声明的作用域。 有时,显式定界也用于在一个语句块中间插入 return (参见 §3.3.4)。
3.3.2 – 代码块
Lua 的一个编译单元被称为一个 代码块。 从句法构成上讲,一个代码块就是一个语句块。
chunk ::= block
Lua 把一个代码块当作一个拥有不定参数的匿名函数
(参见§3.4.11)来处理。
正是这样,代码块内可以定义局部变量,它可以接收参数,返回若干值。
此外,这个匿名函数在编译时还为它的作用域绑定了一个外部局部变量
_ENV
(参见 §2.2)。
该函数总是把 _ENV
作为它唯一的一个上值,
即使这个函数不使用这个变量,它也存在。
代码块可以被保存在文件中,也可以作为宿主程序内部的一个字符串。 要执行一个代码块, 首先要让 Lua 加载 它, 将代码块中的代码预编译成虚拟机中的指令, 而后,Lua 用虚拟机解释器来运行编译后的代码。
代码块可以被预编译为二进制形式;
参见程序 luac
以及函数 string.dump
可获得更多细节。
用源码表示的程序和编译后的形式可自由替换;
Lua 会自动检测文件格式做相应的处理
(参见 load
)。
3.3.3 – 赋值
Lua 允许多重赋值。 因此,赋值的语法定义是等号左边放一个变量列表, 而等号右边放一个表达式列表。 两边的列表中的元素都用逗号间开:
stat ::= varlist ‘=’ explist
varlist ::= var {‘,’ var}
explist ::= exp {‘,’ exp}
表达式放在 §3.4 中讨论。
在作赋值操作之前, 那值列表会被 调整 为左边变量列表的个数。 如果值比需要的更多的话,多余的值就被扔掉。 如果值的数量不够需求, 将会按所需扩展若干个 nil 。 如果表达式列表以一个函数调用结束, 这个函数所返回的所有值都会在调整操作之前被置入值列表中 (除非这个函数调用被用括号括了起来;参见 §3.4)。
赋值语句首先让所有的表达式完成运算, 之后再做赋值操作。 因此,下面这段代码
i = 3
i, a[i] = i+1, 20
会把 a[3]
设置为 20,而不会影响到 a[4]
。
这是因为 a[i]
中的 i
在被赋值为 4 之前就被计算出来了(当时是 3 )。
简单说 ,这样一行
x, y = y, x
会交换 x
和 y
的值,
及
x, y, z = y, z, x
会轮换 x
,y
,z
的值。
对全局变量以及表的域的赋值操作的含义可以通过元表来改变。
对 t[i] = val
这样的变量索引赋值,
等价于 settable_event(t,i,val)
。
(关于函数 settable_event
的详细说明,参见
§2.4。
这个函数并没有在 Lua 中定义出来,也不可以被调用。
这里我们列出来,仅仅出于方便解释的目的。)
对于全局变量 x = val
的赋值等价于
_ENV.x = val
(参见 §2.2)。
3.3.4 – 控制结构
if , while , and repeat 这些控制结构符合通常的意义,而且也有类似的语法:
stat ::= while exp do block end
stat ::= repeat block until exp
stat ::= if exp then block {elseif exp then block} [else block] end
Lua 也有一个 for 语句,它有两种形式 (参见 §3.3.5)。
控制结构中的条件表达式可以返回任何值。 false 与 nil 两者都被认为是假。 所有不同于 nil 与 false 的其它值都被认为是真 (特别需要注意的是,数字 0 和空字符串也被认为是真)。
在 repeat –until 循环中, 内部语句块的结束点不是在 until 这个关键字处, 它还包括了其后的条件表达式。 因此,条件表达式中可以使用循环内部语句块中的定义的局部变量。
goto 语句将程序的控制点转移到一个标签处。 由于句法上的原因, Lua 里的标签也被认为是语句:
stat ::= goto Name
stat ::= label
label ::= ‘::’ Name ‘::’
除了在内嵌函数中,以及在内嵌语句块中定义了同名标签,的情况外, 标签对于它定义所在的整个语句块可见。 只要 goto 没有进入一个新的局部变量的作用域,它可以跳转到任意可见标签处。
标签和没有内容的语句被称为空语句,它们不做任何操作。
break 被用来结束 while 、 repeat 、或 for 循环, 它将跳到循环外接着之后的语句运行:
stat ::= break
break 跳出最内层的循环。
return 被用于从函数或是代码块(其实它就是一个函数) 中返回值。 函数可以返回不止一个值,所以 return 的语法为
stat ::= return [explist] [‘;’]
return 只能被写在一个语句块的最后一句。
如果你真的需要从语句块的中间 return ,
你可以使用显式的定义一个内部语句块,
一般写作 do return end
。
可以这样写是因为现在 return 成了(内部)语句块的最后一句了。
3.3.5 – For 语句
for 有两种形式:一种是数字形式,另一种是通用形式。
数字形式的 for 循环,通过一个数学运算不断地运行内部的代码块。 下面是它的语法:
stat ::= for Name ‘=’ exp ‘,’ exp [‘,’ exp] do block end
block 将把 name 作循环变量。 从第一个 exp 开始起,直到第二个 exp 的值为止, 其步长为第三个 exp 。 更确切的说,一个 for 循环看起来是这个样子
for v = e1, e2, e3 do block end
这等价于代码:
do
local var, limit, step = tonumber(e1), tonumber(e2), tonumber(e3)
if not (var and limit and step) then error() end
var = var - step
while true do
var = var + step
if (step >= 0 and var > limit) or (step < 0 and var < limit) then
break
end
local v = var
block
end
end
注意下面这几点:
- 所有三个控制表达式都只被运算一次, 表达式的计算在循环开始之前。 这些表达式的结果必须是数字。
- var,limit,以及 step 都是一些不可见的变量。 这里给它们起的名字都仅仅用于解释方便。
- 如果第三个表达式(步长)没有给出,会把步长设为 1 。
- 你可以用 break 和 goto 来退出 for 循环。
- 循环变量
v
是一个循环内部的局部变量; 如果你需要在循环结束后使用这个值, 在退出循环前把它赋给另一个变量。
通用形式的 for 通过一个叫作 迭代器 的函数工作。 每次迭代,迭代器函数都会被调用以产生一个新的值, 当这个值为 nil 时,循环停止。 通用形式的 for 循环的语法如下:
stat ::= for namelist in explist do block end
namelist ::= Name {‘,’ Name}
这样的 for 语句
for var_1, ···, var_n in explist do block end
它等价于这样一段代码:
do
local f, s, var = explist
while true do
local var_1, ···, var_n = f(s, var)
if var_1 == nil then break end
var = var_1
block
end
end
注意以下几点:
- explist 只会被计算一次。 它返回三个值, 一个 迭代器 函数, 一个 状态 , 一个 迭代器的初始值。
- f, s,与 var 都是不可见的变量。 这里给它们起的名字都只是为了解说方便。
- 你可以使用 break 来跳出 for 循环。
- 环变量 var_i 对于循环来说是一个局部变量; 你不可以在 for 循环结束后继续使用。 如果你需要保留这些值,那么就在循环跳出或结束前赋值到别的变量里去。
3.3.6 – 函数调用语句
为了允许使用函数的副作用, 函数调用可以被作为一个语句执行:
stat ::= functioncall
在这种情况下,所有的返回值都被舍弃。 函数调用在 §3.4.10 中解释。
3.3.7 – 局部声明
局部变量可以在语句块中任何地方声明。 声明可以包含一个初始化赋值操作:
stat ::= local namelist [‘=’ explist]
如果有初始化值的话,初始化赋值操作的语法和赋值操作一致 (参见 §3.3.3 )。 若没有初始化值,所有的变量都被初始化为 nil。
一个代码块同时也是一个语句块(参见 §3.3.2), 所以局部变量可以放在代码块中那些显式注明的语句块之外。
局部变量的可见性规则在 §3.5 中解释。