lua基礎——基本文法

來源:互聯網
上載者:User
類型

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))

運行結果跟之前那個一樣。

協程是非搶佔式的,因此如果一個線程阻塞了,整個程式也就停止了。(當然這個也是有辦法解決的,不過在基礎部分不講)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.