资料摘自<Lua程序设计(第二版)>
语句
赋值
a = "hello".."world"
t.n = t.n + 1
c,b = 10,2*x --多重赋值,也就是一下子将多个值赋予多个变量,每个值或每个变量之间以逗号分隔,赋值后c为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
print(a,b,c) --> 0 1 nil
a,b = a + 1,b + 1, b+2 --其中b+2会被忽略
print(a,b) -->1 2
a,b,c = 0
print(a,b,c) -->0 nil nil
局部变量与块(block)
相对于全局变量,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
使用do-end可以显式地界定一个块,每当输入了do时,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
print(a,b) -->1 10
在Lua中,有一种习惯写法是:
local foo = foo
这句代码创建了一个局部变量foo,并将用全局变量foo值初始化它。如果后续其他函数改变了全局foo的值,那么可以在这里先将它的值保存起来。这种方式还可以加速在当前作用域中对foo的访问。
控制结构
Lua提供了一组传统的、小巧的控制结构,包括用于条件执行的if,用于迭代的while、repeat和for。所有的控制结构都有一个显式的终止符:if、for和while以end作为结尾,repeat以until作为结尾。
控制结构中的条件表达式可以是任何值,Lua将所有不是false和nil的值视为"真"。
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语句。
while
与其他语言中的while循环一样,Lua先测试while的条件。如果条件为假,那么循环结束;不然,Lua执行循环体,并重复这一过程。
local i = 1
while a[i] do
print(a[i])
i = i + 1
end
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
数字型for
for语句有两种形式:数字型for和泛型for
数字型for的语法如下:
for var = exp1,exp2,exp3 do
<执行体>
end
var从exp1变化到exp2,每次变化都以exp3作为步长递增var,并执行一次"执行体"。第三个表达式exp3是可选的,若不指定的话,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.huge do
if(0.3*i^3-20*i^2-500 >= 0) then
print(i)
break
end
end
为了更好际使用for循环,还需要了解一些小细节。首先,for的3个表达式是在循环开始前一次求值的。例如,上例中的f(x)只会执行一次。其次,控制变量会被自动地声明为for语句的局部变量,并且仅在循环体内可见。因此,控制变量在循环结束后就不存在了:
for i =1 ,10 do print(i) end
max = i --可能是错误的。这是访问的是一个全局的i
如果需要在循环结束后访问控制变量的值,必须将该值保存到另一个变量中:
--在一个列表中查找一个值
local found = nil
for i = 1,#a do
if a[i]<0 then
found = i --包含i的值
break
end
end
print(found)
最后一点,不要在循环过程中修改控制变量的值,否则会导致不可预知的效果。如果想在for循环正常结束前终止循环,可以像上例中那样使用break语句。
泛型for
泛型for循环通过一个迭代器函数来遍历所有值:
--打印数组a的所有值
for i,v in ipairs(a) do print(v) end
Lua的基础库提供了ipairs,这是一个用于遍历数组的迭代器函数。在每次循环中,i会被赋予一个索引值,同时v被赋予一个对应于该索引的数组元素值。下面是另一个类似的示例,演示了如何遍历一个table中所有的key:
--打印table t中所有的key
for k in paris(t) do print(k) end
从外观上看泛型for比较简单,但其实它是非常强大的。通过不同的迭代器,几乎可以遍历所有的东西,而且写出的代码极具可读性。标准库提供了几种迭代器,包括用于迭代文件中每行的(io.lines)、迭代table元素的(pairs)、迭代数组元素的(ipairs)、迭代字符串中单词的(string.gmatch)等。当然,读者还可以编写自己的迭代器。虽然泛型for的使用非常简单,但编写迭代器函数却有不少细节需要注意。
泛型for循环与数字型for循环有两个相同点:1,循环变量是循环体的局部变量;2,决不应该对循环变量任何赋值
对于泛型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",...)
break与return
break和return语句用于跳出当前的块。
break语句用于结束一个循环,它只会跳出包含它的那个内部循环。return语句用于从一个函数中返回结果,或者用于简单地结束一个函数的执行。任何函数的结尾处都有一句隐式的return。所以如果有一个函数,它没有值需要返回,那么就无须在其结尾处添加return语句。
由于语法构造的原因,break或return只能是一个块的最后一条语句。换句话说,它们应是程序块的最后一条语句,或者是end、else或until前的一条语句。例如,下例中的break就是then块的最后一条语句。
local i=1
while a[i] do
if a[i] == v then break end
i = i+1
end
因为那些位于return或break之后的语句将无法执行到,所以通常只能在上述几个位置使用这些语句。然而有时可能希望在一个块的中间插入一句return或break。例如,准备调试一个函数,但又不想执行该函数的内容。在这种情况下,可以使用一个显式的do块来包住一条return语句:
function foo()
return --<<语法错误
--在下一个块中return就是最后一条语句
do return end --ok
<其他语句>
end