類型
lua是動態類型語言,即變數的類型可以變。
通過type可以測試給定變數的類型,下面是例子:
print(type("helloworld"))<span style="white-space:pre"></span>-->stringprint(type(1))-->numberprint(type(nil))-->nilprint(type(true))-->booleanprint(type(type))-->functionprint(type({x = 0, y = 1}))<span style="white-space:pre"></span>-->tableco = coroutine.create(function()print("helloworld")end)print(type(co))-->thread上面展示了lua的7個基本類型:
string number nil boolean function table thread。
另外還有一個類型是userdata,還不知道如何展示...
幾點說明:
1. nil類型只有nil這麼一個值。
2. boolean有兩個值true和false。另外lua中的所有值都可以用在條件陳述式中,且除了false和nil表示假,其它都表示真。比如0,它表示真。
3. 數實值型別只有number,沒有int、float等類型。
4. string可以用雙引號,也可以用單引號指定。還可以用[[裡面是字串]],[[]]的特點是可以包含多行,可以嵌套,且不解釋轉移字元。
5. function和其他上述的類型一樣,屬於第一類值,就是說也可以存在普通的變數裡面。
6. table、userdata和thread後面還會講。
變數
變數不需要聲明。
給一個變數賦值後就產生了一個全域變數。
print(a)-->nila = 0print(a)-->0a = "hello world"print(a)-->hello worlda = nil-->刪除a這個變數
沒有初始化的全域變數也可以訪問,得到的是nil。實際的意思是,如果一個變數不是nil,就表示變數存在。所以如果給一個變數賦值為nil,就表示刪除這個變數。
使用local關鍵字可以建立局部變數。
dolocal aendprint(a)-->列印nil,因為a的生命週期已經結束
局部變數只在被聲明的那個代碼塊內有效。代碼塊可以是一個控制結構,一個函數體,或者一個chunk(變數被聲明的那個檔案或者字串)。
lua中可以使用多變數賦值:
a,b,c = 1,"helloworld"print(a)-->1print(b)-->helloworldprint(c)-->nil
多變數賦值也不是很講究,=右邊如果比左邊少,則相應的變數為nil,比如這裡的c;=右邊如果比左邊少,則多出來的賦值會被忽略。
多變數賦值在函數返回時也很有用,因為lua中的函數是可以返回多個值的。
運算式
一些特別的點:
1. ~=相當於c語言裡面的!=,不等於。
2. table、userdata和function是引用比較,只有兩個變數指向同一個對象才是相等。
3. 邏輯運算子是"and or not",但是這裡的and和or意思跟c語言有不同:
a and b:如果a為false,則返回a,否則返回b;
a or b :如果a為true,則返回a,否則返回b。
4. “..”兩個點,表示字元串連符;如果運算元是number,則轉換為字串:
print(1 .. 2)-->12
注意這裡1 ..之間有一個空格,不然會報錯。
但是如果是字串就不需要:
print("hello".."world")-->helloworld
表的構造
下面是基本例子:
table1 = {}-->空表names = {"jack", "john", "jimmy"}-->列表形式初始化print(names[1])<span style="white-space:pre"></span>-->jack,下標從1開始names[4] = "jason"print(names[4])<span style="white-space:pre"></span>-->jasonprint(names[5])<span style="white-space:pre"></span>-->nil,因為不存在a = {x = 0, y = 1}-->record形式初始化print(a.x)-->0print(a["x"])<span style="white-space:pre"></span>-->0,另一種表示方式b = {"helloworld", x = 1, "goodbye"}-->混合形式print(b[1])-->helloworldprint(b.x)-->1print(b[2])-->goodbye---print(b.1)---沒有這種
表中分隔可以使用逗號,也可以使用分號。
還有一種更一般的形式:
a = {["+"] = "add", ["-"] = "sub", ["*"] = "mul", ["/"] = "div", }print(a["+"])<span style="white-space:pre"></span>-->addb = {[1] = "one", [2] = "two", [3] = "three"}print(b[1])-->one
控制語句
IF語句:
if xxx then xxx end;
if xxx then xxx else xxx end;
if xxx then xxx elseif xxx then xxx else xxx end; //elseif可以有很多個,注意else和if中間沒有空格。。
WHILE語句:
while xxx do xxx end;
REPEAT-UNTIL語句:
repeat xxx until xxx;
FOR語句:
for var=exp1,exp2,exp3 do xxx end; //這裡for裡面的語句意思是var以exp3為step,從exp1到exp2。
需要注意幾點:
1. 幾個exp只會運行一次,且在for迴圈之前;
2. var是局部變數;
3. 迴圈過程中不要改變var。
for x,y,z in xxx do xxx end;
BREAK和RETURN語句:
break退出當前迴圈;return退出函數並返回結果。
注意break和return只能用在一個block的結尾。如果有時候確實想要在另外的地方使用,可以用這樣的方式:do break/return end;
函數
函數的定義:
function funcName (args) states;end;
關於函數調用時,如果沒有參數,需要使用();
如果參數是字串或者表構造時,可以不用()。
lua函數中實參和形參的匹配也比較不講究,實參多於形參,多餘部分會忽略;實參少於形參,少的形參會是nil。這個與多變數賦值方式一致。
函數可以返回多個值:
function func (void)return 1,2end;a,b = func()print(a)-->1print(b)-->2
通過()可以是函數強制返回一個值:
function func (void)return 1,2end;a,b = func()print(func())-->1 2print((func()))-->1
lua可以有可變參數。使用...三個點表示 ,在函數中用一個叫arg的table表示參數,arg中還有一個域n表示參數的個數 。
下面是一個例子:
function func(...)-- local arg = {...}for i,v in ipairs({...}) doprint(i,v)endend;func(0,1,2,3)列印的結果是:
1 0
2 1
3 2
4 3
函數參數傳遞時可以使用表的形式,這樣就不需要記太多的參數,如下面的例子:
-->不需要特別建立函數func(new, old)function func (args)print(args.new,args.old)endvar = {new = 1, old = 2}func(var)
函數實際上也是變數,因此可以有另外一種表達方式,比如下面兩種方式:
function foo(x) return 2 * x end
foo = function (x) return 2 * x end
它們其實是一樣的。第二個foo被稱為第一類值函數,將它應用在表中是lua物件導向和包機制的關鍵。
函數中還可以包含函數,這個內部的函數可以訪問包含它的那個函數中的變數(包括參數),下面是一個例子:
-->定義了一個返回函數的函數function NewCounter()local i = 0return function()i = i + 1return iendendc1 = NewCounter()-->c1是被返回的函數,需要注意的是它捕獲了NewCounter()中的i變數print(c1())-->1print(c1())-->2c2 = NewCounter()print(c2())-->1-->c2是一個新的返回函數,它捕獲的還是原來的i,所以列印的是1
這裡的內建函式以及它能夠訪問到的(捕獲的)變數,共同構成了稱為"閉包(closure)"的概念。
典型的閉包包含兩個部分,一個是閉包自己,另一個是工廠(建立閉包的函數)。
lua中的函數是變數,因此它可以是全域變數後者局部變數。作為局部變數的例子,比如說表中的域(像io.read這種,read就是一個局部函數):
Lib = {}-->定義一個表Lib.add = function(x, y) return x + y end-->建立兩個局部變數Lib.sub = function(x, y) return x - y endprint(Lib.add(1,1))-->2print(Lib.sub(1,1))-->0表中的局部函數還有兩中形式可以表示:
第二種:
Lib = {add = function(x, y) return x + y end,-->分隔字元別忘了sub = function(x, y) return x - y end}-->定義一個表print(Lib.add(1,1))-->2print(Lib.sub(1,1))-->0第三種:
Lib = {}function Lib.add(x, y) return x + y endfunction Lib.sub(x, y) return x - y end
另外,在函數前面聲明local就得到局部函數:
dolocal foo = function(x)print(x)endlocal function goo(x)print(x)endfoo(1)goo(2)end-->foo(1)報錯,訪問不到了-->goo(2)報錯,訪問不到了
聲明局部函數的時候需要注意遞迴的情況:
local func -->因為有兩個地方要聲明,如果只在第一個位置,即function前面聲明local,-->後面的func(x - 1)位置就會識別不出來它是局部的,所以去找全域的了,-->結果沒有找到就會報錯,所以這裡要做前向聲明func = function (x)if x == 0 thenreturn 1elsereturn x * func(x - 1)endendprint(func(3))
另外lua中的函數還支援“正確的尾調用”,它像下面的樣子:
function f(x) return g(x)end
在這裡g的調用就是尾調用,這種情況下,實際上g返回時不需要返回到調用者f中,所以不需要在棧中保留調用者f的任何資訊。而正確的尾調用就是指lua能夠使尾調用時確實不使用額外的資訊,這相當於一個goto到了另外的函數。
正確的尾調用使得遞迴可以無限進行而不會導致棧的溢出,當然也不只是在遞迴中,其它多個函數之間的調用也不需要擔心棧溢出。
但是需要注意尾調用的形式,像下面這些就不是尾調用:
function f() g() return endreturn g(x) + 1return x or g(x)return (g(x))
迭代器和泛型for
lua中的迭代器常用函數來描述迭代器,每次調用該函數就返回集合的下一個元素。
下面是一個例子:
function IterFactory(t)-->建立迭代器的工廠local i = 0local n = #t-->擷取表的長度,只對列表形式的有效return function()-->這個是迭代器i = i + 1if i <= n thenreturn t[i]endendendaTable = {"one", "two", "three"}f = IterFactory(aTable)print(f())-->oneprint(f())-->twoprint(f())-->threeprint(f())-->不列印上面的例子其實跟《函數》一章中的閉包例子沒有多大的差別。這個主要要講的是下面這種和泛型for一起使用的情況:
function IterFactory(t)-->建立迭代器的工廠local i = 0local n = #t-->擷取表的長度,只對列表形式的有效return function()-->這個是迭代器i = i + 1if i <= n thenreturn t[i]endendendaTable = {"one", "two", "three"}for element in IterFactory(aTable) doprint(element)end泛型for的格式是下面這樣的:
for <var-list> in <exp-list> do <body>end
其中,var-list的第一個變數稱為控制變數,如果它是nil,迴圈終止;exp-list通常是一個迭代工廠的調用,就像前面的IterFactory(aTable)。
泛型for迴圈的執行過程如下:
1. 計算in後面運算式的值,這個運算式返回三個值,分別是迭代函數,狀態常量和控制變數,如果返回的不夠,就用nil來補充。
2. 將狀態常量和控制變數作為參數調用迭代函數。(對於for迴圈,狀態常量沒有用,僅僅在初始化時擷取它的值並傳遞給迭代函數)
3. 將迭代函數返回的值賦值給變數列表。
4. 如果返回的第一個值是nil,迴圈結束,否則執行迴圈體;
5. 回到第二步再次調用迭代函數。
所以說,上面的泛型for可以解釋稱下面的虛擬碼:
dolocal _f, _s, _var = exp-listwhile true dolocal var_1, ..., var_n = _f(_s, _var)_var = var_1if _var == nil thenbreakend<block>endend
編譯/運行/調試 首先介紹chunk的概念:
chunk是一系列的代碼。lua執行的每一個塊代碼,比如一個檔案或者互動模式下(命令列下運行lua不加參數,就會進入互動模式)的每一行都是一個chunk。
chunk可以是一個語句(比如:互動模式下的一行,do end內部只有一行的話也算),可以是一系列的語句(比如do end內部,或者for迴圈內部),還可以是函數。
下面介紹幾個函數:
dofile():用來串連外部的chunk,它負載檔案並執行它。下面是一個例子:
dofile("iterator.lua")這裡的"iterator.lua“是放置上一章節例子的檔案,通過dofile()就可以運行它的代碼。
dofile是lua運行代碼的一種原始操作,它只是輔助作用,真正完成功能的是loadfile()函數。
loadfile():它編譯代碼成中間碼並且返回編譯後的chunk作為函數,但不執行代碼。
注意lua語言也有編譯這個動作,雖然它被稱為指令碼語言,但是lua的編譯器是語言運行時的一部分,所以執行編譯並產生中間碼的動作非常快。
另外,loadfile()不會拋出錯誤資訊而是返回錯誤碼,當發生錯誤時,loadfile()返回nil和錯誤資訊。
與loadfile()很相似的有一個loadstring():
loadstring():從一個字串中讀入代碼並編譯成函數。
f = loadstring("i = 1; print(i)")f()-->這裡才是執行,前面只是產生了函數或者簡寫成:
loadstring("i = 1; print(i)")()-->注意後面的括弧
loadstring()和loadfile()一樣,不會拋出錯誤,但是會返回nil和錯誤資訊,下面是一個例子:
print(loadstring("i i"))它列印如下的錯誤資訊:
需要注意的一點,loadstring()總是在全域環境下編譯字串,所以它只認全域的變數而看不到局部變數:
local i = 0loadstring("print(i)")()-->nil
還有一個跟dofile()類似的函數是require():
require():它用來載入運行庫。與dofile的主要區別是以下幾點:
1. require()會搜尋目錄負載檔案。
2. require()會判斷是否檔案已經載入以避免重複載入同一檔案。
關於第一個區別:require()搜尋的路徑採用模式列表的形式,比如如下的形式:
?;?.lua;/usr/local/lua/?/?.lua
在實際的操作中,比如require("helloworld"),則參數代替模式中的?,並搜尋代替後的檔案:helloworld;helloworld.lua;/usr/local/lua/helloworld/helloworld.lua
下面是一個例子:
實際上它運行了helloworld.lua檔案。 需要注意這裡的路徑儲存在LUA_PATH這個全域變數中,但是它也不一定存在,如果不存在,就用預設的"?;?.lua"。
關於第二個區別:lua中有一個表記錄所有已經載入了得檔案,但是需要注意表中儲存的是檔案的虛名,即參數名字。因此如果使用require("helloworld")和require("helloworld.lua"),那麼實際上還是會載入兩次helloworld.lua檔案,而不是一次。
error():結束程式並返回錯誤資訊。下面是一個例子:
assert():接受兩個參數,第一個參數如果不為真,就調用error(第二個參數表示的資訊)。
pcall():封裝可能的錯誤碼(錯誤碼被放在函數中,或者封裝成一個匿名函數),用來進行錯誤處理。如果沒有錯誤,返回true和調用函數的傳回值,否則返回false加錯誤資訊。
下面是一個例子:
-->可能出錯的代碼封裝在函數內function func() error()-->拋出錯誤end-->pcall調用函數if pcall(func) then print("no error")elseprint("error")end上述的代碼列印error。
不僅是錯誤資訊,當發生錯誤的時候,所有傳遞給error()的參數都會被pcall()返回:
-->可能出錯的代碼封裝在函數內function func() error({code = 404})-->拋出錯誤end-->pcall調用函數local status, err = pcall(func)if status == false then print(err.code)end列印結果:404。
協程coroutine
協程跟線程類似,不同的是同一時間可以跑多個線程,但是協程只能有一個在運行。
協程通過協作來完成,lua裡面就是resume()和yeild()。
協程需要啟動並執行代碼被封裝在函數中,通過將函數名傳遞給create()函數作為參數來建立一個協程,當然也可以直接傳遞匿名函數當作參數。
下面是一個例子:
co = coroutine.create(function ()print("helloworld")end)print(co)-->thread: 0xxxxx(某個16位地址)協程有三個狀態:掛起態,運行態,停止態。
協程被建立後處於掛起態。也就是說協程並不會自動運行。
通過協程的status()來查看狀態:
co = coroutine.create(function ()print("helloworld")end)print(coroutine.status(co))-->suspended
通過resume()可以使協程進入運行態,之後代碼結束就進入了停止態:
co = coroutine.create(function ()print(coroutine.status(co))end)coroutine.resume(co)-->runningprint(coroutine.status(co))-->dead
通過yield()可以將協程掛起:
co = coroutine.create(function ()for i=1,10 doprint("co", i)coroutine.yield()endend)coroutine.resume(co)print(coroutine.status(co))-->suspended運行結果如下:
如果之後再運行resume():
coroutine.resume(co)coroutine.resume(co)coroutine.resume(co)coroutine.resume(co)
運行結果如下:
當調用次數超過10次之後:
print(coroutine.resume(co))
運行結果如下:
resume()和yield()之間可以傳遞參數,見下面的樣本:
樣本一,resume()的參數傳遞給了協程:
co = coroutine.create(function (a, b, c)print("co", a, b, c)end)coroutine.resume(co, 1, 2, 3)-->resume的參數傳遞到了協程中
樣本二,yield()的參數也將傳遞給resume():
co = coroutine.create(function(a, b)coroutine.yield(a + b, a - b)end)print(coroutine.resume(co, 20, 10))
運行結果中,true表示resume()運行成功,後面的30和10是yield的參數。
樣本三,協程代碼傳回值也會傳遞給resume():
co = coroutine.create(function(a, b)return a + b, a - bend)print(coroutine.resume(co, 20, 10))
運行結果跟之前那個一樣。
協程是非搶佔式的,因此如果一個線程阻塞了,整個程式也就停止了。(當然這個也是有辦法解決的,不過在基礎部分不講)