Lua支持的常规语句基本上和C语言所支持的那些语句差不多。这些语句包括赋值、控制结构和过程调用。另外Lua还支持一些不太常见的语句,例如多重赋值和局部变量声明。


1、赋值

赋值的基本含义是修改一个变量或一个table中字段的值:

a = "Hello" .. "World"
t.n = t.n + 1

Lua允许"多重赋值",也就是一下子将多个值赋予多个变量。每个值或每个变量之间以逗号分隔。例如:

a,b = 10,2*x      -- a=10,b=2*x

在多重赋值中,Lua先对等号右边的所有元素求值,然后才执行赋值。这样便可以用一句多重赋值来交互两个变量了,例如:

x,y = y,x       -- 交换x,y
a[i],a[j] = a[j],a[i] -- 交换a[i],a[j]

Lua总是会将等号右边值的个数调整到与左边变量的个数相一致。规则是:若值的个数少于变量的个数,那么多余的变量会被赋值为nil;若值的个数更多的话,那么多余的值会被“悄悄地”丢弃掉。

a,b,c = 0,1       -- a=0,b=1,c=nil
a,b = a+1,b+1,b+2 -- a=1,b=2
a,b,c = 0 -- a=0,b=nil,c=nil

所以若要初始化一组变量,应为每个变量提供一个值;


2、局部变量与块

相对于全局变量,Lua还提供了局部变量。通过local语句来创建局部变量:

j = 10      -- 全局变量
local i = 1 -- 局部变量

与全局变量不同的是,局部变量的作用于仅限于声明它们的那个块。一个块(block)是一个控制结构的执行体、或者是一个函数的执行体,再或者是一个程序块(chunk)。

x = 10                      -- 全局变量
local i = 1 -- 局部变量

while i <= x do
local x = i*2 -- while 循环体中的局部变量
print(x) --> 2,4,6,8,...
i = i+1
end

if i > 20 then
local x -- then中的局部变量
x = 20
print(x + 2) -- 如果测试成功会打印22
else
print(x) --> 10(全局变量)
end

print(x) --> 10(全局变量)

如果在交互模式下,输入一条指令,Lua就会马上运行这句话,并未下一行的运行开启一个新的程序块。为了让解释器一起执行程序块,可以通过​​do-end​​关键字显式地声明程序块,这样Lua就不会单独地执行后面每行的内容,而是直至遇到一个相应的end时,才会执行整个块的内容,例如:

do
local a2 = 2*a
local d = (b^2 - 4*a*c)^(1/2)
x1 = (-b + d)/a2
x2 = (-b - d)/a2
end -- a2和d的作用域至此结束
print(x1,x2)

尽可能地使用局部变量是一种良好的编程风格,例如如下:

  • 局部变量可以避免将一些无用的名称引入全局环境,避免搞乱了全局环境;
  • 访问局部变量比访问全局变量更快;
  • 一个局部变量通常会随着其作用域的结束而消失,这样便使垃圾收集器可以释放其值;

Lua将局部变量的声明当做语句来处理,因此可以在任何允许书写语句的地方书写局部变量的声明。所声明的局部变量的作用于从声明语句开始,直至所在块的结尾。声明语句中还可以包含初始化赋值,其规则与普通的赋值语句完全一样:额外的值会被丢弃;额外的变量会被赋值为nil。如果一条声明语句没有初始化赋值,那么它声明的所有变量都会初始化为nil。

local a,b = 1,10
if a<b then
print(a) --> 1
local a -- 具有隐式的“=nil”
print(a) --> nil
end -- then块至此结束
print(a,b) --> 1 10

在Lua中,有一种习惯用法是:

local foo =

这句代码创建了一个局部变量foo,并将用全局变量foo的值初始化它。如果后续其他函数改变了全局foo的值,那么可以在这里先将它的值保存起来。这种方式还可以加速在当前作用域中对foo的的访问。

由于许多语言都强制程序员在一个块(或一个过程)起始处声明所有的局部变量,所以某些人就认为在一个块的中间使用声明语句是一种不好的习惯。但事实恰恰相反,在需要时才声明变量,可以使这个变量在初始化时刻就拥有一个有意义的初值。此外,缩短变量的作用域有助于提高代码的可读性。


3、控制结构

Lua提供了一组传统的、小巧的控制结构,包括用于条件执行的​​if​​​,用于迭代的​​while​​​、​​repeat​​​和​​for​​​。所有的结构都有一个显式的终止符:​​if​​​、​​for​​​和​​while​​以end作为结尾,​​repeat​​以until作为结尾。

控制结构中的条件表达式可以是任何值,Lua将所有不是false和nil的值视为“真”。


3.1、if then else

if语句先测试其条件,然后根据测试结果执行then部分或else部分,else部分是可选的;

if a<0 then a=0 end
if a<b then return a else return b end
if line>MAXLINES then
showpage()
line = 0
end

若要编写嵌套的if,可以使用elseif,它类似于else后面紧跟着一个if,它还可以避免在这样的嵌套中出现多个end:

if op == "+" then
r = a+b
elseif op == "-" then
r = a-b
elseif op == "*" then
r = a*b
elseif op == "/" then
r = a/b
else
error("invalid operation")
end

由于Lua不支持switch语句,所以这种一连串的if-elseif代码是很常见的。


3.2、while

与其它语言中的while循环一样,Lua先测试while的条件。如果条件为假,那么循环结束;不然,Lua执行循环体,并重复这一过程。

local i = 1
while a[i] do
print(a[i])
i = i+1
end

3.3、repeat

正如这个名字所暗示的那样,一条​​repeat-until​​语句重复执行其循环体直到条件为假时结束。测试是在循环体之后做的,因此循环体至少会执行一次。

-- 打印输入的第一行不为空的内容
repeat
line = io.read()
until line ~= ""
print(line)

与其它大多数语言不同的是,在Lua中,一个声明在循环体中的局部变量的作用于包括了条件测试:

local sqr = x/2
repeat
sqr = (sqr + x/sqr)/2
local error = math.abs(sqr^2 - x)
until error < x/10000 -- 此处仍可以访问error

3.4、数字型 for

for语句有两种形式:数字型for和泛型for

数字型for的语法如下:

for var=Init,Term,Step do
<执行体>
end

var从Init变化到Term,每次变化都以Step作为步长递增var,并执行一个“执行体”。第三个表示式Step是可选的,若不指定的话,Lua会将步长默认为1,示例如下:

for i=1,f(x) do
print(i)
end

for i=10,1,-1 do
print(i)
end

如果不想给循环设置上限的话,可以使用常量​​math.huge​​:

for i=1,math.hugh do
if(0.3*i^3 - 20*i^2 -500 >= 0) then
print(i)
break
end
end

为了更好地使用for循环,还需要了解一些小细节。首先,for的3个表达式是在循环开始前一次性求值的。例如,上例中的f(x)只会执行一次。其次,控制变量会被自动声明为for语句的局部变量,并且仅在循环体内可见,因此,控制变量在循环结束后就不存在的。

如果需要在循环结束后访问控制变量的值(通常是在break循环时),必须将该值保存到另一个变量中,例如:

-- 在一个列表中查找一个值
local found = nil
for i=1,#a do
if a[i]<0 then
found = i
break
end
end
print(found)

最后一点,不要在循环过程中修改控制变量的值,否则会导致不可预知的效果。如果想在for循环正常结束前终止循环,可以使用​​break​​语句。


3.5、泛型 for

泛型for循环通过一个迭代器(iterator)函数来遍历所有值:

-- 打印数组a的所有值
for i,v in ipairs(a) do
print(v)
end

Lua的基础库提供了​​ipairs​​ ,这是一个用于遍历数组的迭代器函数。在每次循环中,i会被赋予一个索引值,同时v被赋予一个对应于该索引的数组元素值。

-- 打印table t中的所有key
for k in pairs(t) do
print(k)
end

从外观上看泛型for比较简单,但其实它是非常强大的。通过不同的迭代器,几乎可以遍历所有的东西,而且写出的diam极具可读性。标准库提供了几种迭代器,例如:

迭代器

作用

io.lines

迭代文件中的每行

pairs

迭代table元素

ipairs

迭代数组元素

string.gmatch

迭代字符串中单词

当然,也可以编写自己的迭代器。

泛型for循环与数字型for循环有两个相同点:

  • 循环变量是循环体的局部变量;
  • 决不应该对循环变量作任何赋值;

对于泛型for的使用,再举一个例子。假设有这样一个table,它的内容是一周中每天的名称:

days = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}

现在要将一个名称转换成它在一周的的位置,。为此,需要根据给定的名称来搜索这个table。然后在Lua中,通常更有效的方法是创建一个“逆向table”。例如这个逆向table叫revDays,它以一周中每天的名称作为索引,位置数字作为值:

revDays = {["Sunday"]=1,["Monday"]=2,["Tuesday"]=3,["Wednesday"]=4,["Thursday"]=5,["Friday"]=6,["Saturday"]=7}

接下来,要找出一个名称所对应的序号,只需用名字来索引这个reverse table即可:

x = "Tuesday"
print(revDays[x]) --> 3

当然,不必手动声明这个逆向table,而是通过原来的table自动地构造出这个逆向table:

revDays = {}
for k,v in pairs(days) do
revDays[v] = k
end

这个循环会为每个元素赋值,其中变量k为key(1、2、…),变量v为value(“Sunday”、“Monday”、…)。


4、break与return

​break​​​和​​return​​语句用于跳出当前的块。

​break​​语句用于结束一个循环,它只会跳出包含它的那个内部循环(for、repeat和while),而不会改变外层的循环。在执行了break后,程序会在那个被跳出的循环之外继续执行。

​return​​语句用于从一个函数中返回结果,或者用于简单地结束一个函数的执行。任何函数的结尾处都有一句隐式的return。所以如果有一个函数,它么有值需要返回,那么就无须在其结尾处添加return语句。

由于语法构造的原因,break和return只能是一个块的最后一条语句。