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 中解释。