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
~~~
水平有限,如果有朋友發現錯誤,歡迎留言交流。