💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
Lua支持大多数传统的语句,跟C语言和Pascal差不多。传统的语句包括:赋值,控制结构,流程调用等。Lua还支持一些不太传统的语句,例如多赋值(听起来有点怪,往下看就明白了)和局部变量声明(这个好像也是传统的吧)。 ## 1. 赋值 赋值是改变一个变量的值或者table的域的最基本的方法: ~~~ a = "hello" .. "world" t.n = t.n + 1 ~~~ Lua支持多赋值,多个值对应于多个变量,值和变量都分别用逗号','隔开。 ~~~ a, b = 10, 20 ~~~ 在上面的语句中,得到的结果为a=10, b=20。 在多赋值语句中,Lua首先计算出所有的值,然后才会做赋值操作。因此,我们可以利用多赋值语句交换两个变量的值,如下: ~~~ x, y = y, x -- swap 'x' for 'y' a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[j]' ~~~ Lua总是根据变量的数目来纠正值的数目:当变量的数目较多时,多出来的变量赋值为nil,相反,如果值的数目过多,那么多于的值会被丢弃: ~~~ a, b, c = 0, 1 print(a, b, c) --> 0 1 nil a, b = a+1, b+1, b+2 -- value of b+2 is discarded print(a, b) --> 1 2 a, b, c = 0 print(a, b, c) --> 0 nil nil ~~~ 上面的例子中,最后一条赋值语句是一个常犯的错误。如果要初始化一系列的变量为0,那么必须为每一个变量提供一个值。 ~~~ a, b, c = 0, 0, 0 print(a, b, c) --> 0 0 0 ~~~ 上面的例子,基本是人为的写出来说明多赋值的用法,在程序中,我们通常是不用多赋值语句来对一系列不相干的变量在同一行上进行赋值。多赋值操作在执行速度上不如同样结果的单赋值语句。但是,有时我们确实需要使用多赋值语句,例如上面的交换两个变量的值。还有一个用途是,可以获取函数的多个返回值。这种情况下,就是一个表达式为多个变量提供值。例如,a, b = f(),对函数f的调用返回两个值:a得到第一个,b得到第二个。 ## 2. 局部变量和语句块 除了全局变量,Lua也支持局部变量。可以用**local**来创建局部变量 ~~~ j = 10 -- global variable local i = 1 -- local variable ~~~ 跟全局变量不同,局部变量有作用域,只在它被声明的域内有效。域可以是一个控制结构,一个函数,或者代码块(变量声明的文件或者代码块) ~~~ x = 10 local i = 1 -- local to the chunk while i <= x do local x = i*2 -- local to the while body print(x) --> 2, 4, 6, 8, ... i = i + 1 end if i > 20 then local x -- local to the then body x = 20 print(x + 2) --(would print 22 if test succeeded) else print(x) --> 10 (the global one) end print(x) --> (the global one) ~~~ 如果上面的示例在交互模式下运行,可能会得不到预期的结果。在交互模式下,每一行是一个块。一旦你输入示例的第2行(local i = 1),Lua立刻运行它并且在下一行开始一个新的块。那时,**local**声明已经无效。当然,解决这个问题的办法肯定是有的。我们可以用**do-end**来显式地对整个块取消限制。只要你输入了**do**,那么命令只有在得到相对于的**end**时才会完成,因此Lua不会自己执行每一行。 在你想更好的控制局部变量的作用域时,**do**语句块是非常好用的: ~~~ do local a2 = 2*a local d = (b^2 - 4*a*c)^(1/2) x1 = (-b + d)/a2 x2 = (-b - d)/a2 end -- scope of 'a2' and 'd' ends here print(x1, x2) ~~~ 尽可能的使用局部变量,是一个好的编程方式。局部变量可以使你避免用很多多余的全局变量名字导致的程序混乱。除此之外,局部变量的访问速度要比全局变量快。还有,局部变量在超出作用域后会自动失效,垃圾收集器会释放掉它占用的空间。 Lua将局部变量声明看成是语句。这样,你可以在任何地方code局部变量声明。局部变量的作用域,从它声明的地方开始生效,到语句块结束时失效。每条声明语句可以包含一个初始化,像常规的初始化语句一样:多余的值被丢弃,多余的变量赋值为nil。如果声明不包含初始化赋值,那么就会默认初始化为nil: ~~~ local a, b = 1, 20 if a < b then print(a) --> 1 local a -- '=nil' is implicit print(a) --> nil end print(a, b) -->1 10 ~~~ 一个很常用的Lua语句: ~~~ local foo = foo ~~~ 这句代码创建一个局部变量foo,并且用全局变量foo的值来对它进行初始化。(局部的foo在声明后可见)。有时,语句块想保护foo的值,防止后来它的值被其他函数改变,这种情况下,这条语句就很有用了;当然,也可以加快对foo的访问速度。 因为很多语句强制要求所有的局部变量都必须在一个块的开始处进行声明,一些人会认为在代码块中间进行局部变量声明不是好的编程风格。但是,恰恰相反:当你要用的时候再声明它,你总是会附带一个初始化的赋值语句(这样你就很少会忘记变量初始化)。除此之外,也可以缩小变量的作用域,增强代码可读性。 ## 3.控制结构 Lua提供了一些传统的控制结构,**if**条件执行,**while**,**repeat**和**for**迭代执行。所有的控制结构都有一个显式的结束符:**end**是**if**,**for**,**while**的结束符;**until**是**repeat**结构的结束符。 控制语句的条件表达式可能是任意值,Lua将**false**和**nil**看作是**false**,其他所有都是**true**。 ### if then else **if**语句检查它的条件表达式,根据结果来执行then-part或者else-part。else-part是可选的。 ~~~ 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语句是比较平常的。 ### while **while**检查条件表达式,如果条件为**false**,那么循环停止;否则,Lua运行循环体。然后重复这个过程。 ~~~ local i = 1 while a[i] do print(a[i]) i = i + 1 end ~~~ ### repeat 从名字就可以看出,**repeat-until**语句会执行它的循环体,直到它的条件表达式为**true**。条件表达式实在循环体之后才检查,因此,循环体至少会被运行一次。 ~~~ -- print the first non-empty input line 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' still visible here ~~~ ### Numeric for 在Lua中,for循环有两种形式,**numeric for**和**generic for** **numeric for**的语法如下: ~~~ for var = exp1, exp2, exp3 do <something> end ~~~ 这个for循环,用var的值(从exp1到exp2,步进为exp3,exp3缺省默认为1)来执行something循环体。一些代表性的示例如下: ~~~ 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个表达式exp1,exp2,exp3只会在循环开始的时候被计算1次。例如,在之前的示例中,f(x)只被调用一次。第二,控制变量被for语句自动声明为局部变量,并且只在循环中有效。下面的示例说明了一个代表性的错误,认为控制变量i在循环结束后仍然有效: ~~~ for i = 1, 10 do print(i) end max = i -- probably wrong! 'i' here is global ~~~ 如若在循环结束后需要知道控制变量的是值,那么就必须将它的值保存到另外的变量中: ~~~ -- find a value in a list local found = nil for i = 1, #a do if a[i] < 0 then found = i -- save value of 'i' break end end print(found) ~~~ 第三,绝对不能去更改控制变量的值,否则结果是不可预料的。如果想提前结束一个for循环,可以使用break。 ### Generic for **generic for**循环从一个迭代函数中遍历所有的值 ~~~ -- print all values of array 'a' for i, v in ipairs(a) do print(v) end ~~~ Lua基础库提供了一个顺手的迭代函数*ipairs*。每次循环,i得到index,v得到与index相关的值。下面是一个类似的遍历table中的所有key的示例: ~~~ -- print all keys of table 't' for k in pairs(t) do print(k) end ~~~ 从表面上看很简单,但是generic for是比表面上看更强大的。使用合适的迭代器,我们可以遍历几乎任何东西。基础库提供了几个迭代器,迭代文件的行(*io.lines*),table的key-value对(*pairs*),数组中的条目(*ipairs*),string中的字符(*string.gmatch*),等等。当然,我们也可以自己写迭代器。尽管使用generic for很容易,但是写迭代器还是有一些技巧的,这个后面再讨论。 generic for和numeric for有两个相同点:控制变量是循环体中的局部变量,绝不能对控制变量赋值。 让我们再看一个更具体的generic for的示例,假设一个table如下: ~~~ days = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} ~~~ 现在,要把名字转换为其在一周中的位置,例如Sundays转换为1.你可以搜索table,找到相应的name。但是一个更有效率的方法是创建一个reverse table,表名为revDays,以name为key,number为值,看起来应该如下: ~~~ revDays = {["Sundays"] = 1, ["Mondays"] = 2, ["Tuesday"] = 3, ["Wednesday"] = 4, ["Thursday"] = 5, ["Friday"] = 6, ["Saturday"] = 7} ~~~ 那么,你只需要以name为key从revDays中取值就可以了 ~~~ x = "Tuesday" print(revDays[x]) --> 3 ~~~ 当然,我们不需要手动声明这个revDays,我们可以根据原来的Days来创建它: ~~~ revDays = {} for k, v in pairs(days) do revDays[v] = k end ~~~ ### break 和 return **break**和**return**语句可以使程序跳出循环 我们用**break**语句结束一个循环,结束包含它的最内层的循环,这个跟C是类似的。不能在循环体外使用**break**。**break**后,程序继续从跳出的循环部分继续执行。 **return**语句从函数返回结果,或者简单地只是结束一个函数。在函数的末尾都有一个隐式的**return**,因此,如果函数正常结束,不返回任何值,就没必要显式地调用一次**return**。 由于一些语法限制,**break**和**return**语句只能作为一个语句块的最后一句(这个在5.2版本已经没有限制了)。下面是一个示例: ~~~ local i = 1 while a[i] do if a[i] == v then break end i = i + 1 end ~~~ 有时候,在语句块的中间**return**或者**break**也很有必要,例如调试的时候(如下示例),那可以用*do end*来显示构造一个语句块 ~~~ function foo() return --<< SYNTAX ERROR -- 'return' is the last statement in the next block do return end -- ok <other statements> end ~~~ 水平有限,如果有朋友发现错误,欢迎留言交流。