1. 程式人生 > >Lua程式設計學習筆記(一) Lua基本語法(1)

Lua程式設計學習筆記(一) Lua基本語法(1)

Lua基本語法(一)


1.  開始


(1)基本常識

  • lua中的識別符號可以由任意字母、數字、下劃線構成的字串,但不能以數字開頭。
  • 全域性變數不需要申明,直接賦值即可建立,刪除變數直接賦nil。未申明的變數不會報錯,直接返回nil。
  • 行註釋以“–”開頭;塊註釋以“–[[”開始,“]]”結束,重啟塊註釋加“-”即可。(住:塊註釋需獨佔一行)

(2)Lua解析器

  在程式碼檔案中第一行輸入:#!User/local/bin/lua,系統將直接用lua來解析這個指令碼檔案。
  Lua解析器的用法:lua [選項引數] [指令碼[引數]],常用引數有

  • [-i] 用於除錯和手工測試;
  • [-e] 直接輸入程式碼,如:lua -e “print(“hello”)”;
  • [-l] 載入一個lua程式庫;

  在指令碼程式碼中,可以通過全域性變數arg來檢索指令碼的啟動引數。指令碼名稱索引為0(前一個索引為-1),第一個引數為1,依此類推…
  如:lua -e “sin=math.sin”script a b,則arg[0]=script,arg[1]=a,arg[-1]=“sin=math.sin”。

2.   型別與值


  Lua中有8中基本型別:nil(空)、boolean(布林)、number(數字)、string(字串)、userdata(自定義型別)、function(函式)、table(表)、thread(執行緒)。函式type(val)可返回val的型別名。

(1)nil(空)

  nil是一種型別,只有一個值nil,在lua中也叫無效值。將nil賦給一個變數,可刪除變數。

(2)boolean(布林)

  boolean型別只有2個值:false和true。在條件表示式中,只有值為false和nil為“假”,其它值均為“真”。

(3)number(數字)

  number用於表示實數,lua中沒有整數型別。數字常量有2中表示法,分別為普通表示法和科學計數法,如:
4 0.4  4.57e-3   0.3e12   5e+20

(4)string(字串)

  string通常用於表示“一個字元序列”,其表示方式有4中:

  • <1> 單引號表示’;
  • <2> 雙引號表示“”;
  • <3> 用[[和]]包裹住字串,可延申多行,可包含轉移字元;
  • <4> 左邊/右邊2個方括號加任意數量的等號,如:[[…]],可防止字串中出現程式碼註釋;

  在lua中,“…”用於連線字串,特殊地:純數字之間用空格隔一下。tonumber用於將字串轉成數字,tostring則可將數字轉成字串。

(5)function(函式)

  在lua中,函式作為“第一類值”看待。這表示函式可以儲存在變數中,可以通過引數傳遞給其它函式,還可以作為其它函式的返回值。lua既可以呼叫以自身lua語言編寫的函式,又可以呼叫以C語言編寫的函式。

(6)table(表)

  Table型別實現了“關聯陣列”,可以用特殊索引訪問它。為整數索引時,可理解為陣列;為字串索引時,可理解為記錄。基於table,還可實現一些較複雜的資料結構,如:佇列、棧、集合、記錄等。
  Lua僅持有一個對它們的引用,不會產生新的副本或建立table,可理解為兩層含義:

  • table型別的變數都是引用,要保護變數,需要拷貝表;
  • 變數的賦值都是引用,可間接修改原值;

  刪除一個表中元素,可直接賦值nil。Lua中將nil作為陣列的結尾標誌,#t表示陣列長度,若要處理中間元素為nil的“空隙”陣列,可使用table.maxn(t)獲取最大索引。

3.  表示式


(1)算術表示式

  Lua中支援的算術操作符有:二元的+(加)、-(減)、*(乘)、/(除)、^(指數)、%(取模),一元的“-”(負號)。
特別地,x(1/2)表示平方根,x(1/3)表示立方根。
取模操作符的定義:a % b = a - math.floor( a / b ) * b。對於實數有這特殊用途,x % 1表示x的小數部分,x - x % 1表示x的整數部分,x - x % 0.01表示x精確到小數點後兩位的結果。
angle % (2 * math.pi)表示將任意角度angle限制在[0.2Π]範圍內。

(2)關係運算符

  關係運算符的結果只有true和false兩種。
對於基本型別,只對其值作比較;而對於table、函式、userdata型別,lua只作引用比較,只有引用同一個物件時才判定為相等。

(3)邏輯操作符

  邏輯操作符有and、or、not。x=x or {} <> if not x then x={} end;C語言中的a?b:c <> (a and b)or c,其前提條件是:b不為假,否則無意義。
  示例1:比較大小

max=(x>y) and x or y;

(4)字串連線

  Lua中連線2個字串,用…(兩點)表示。連線操作符只會建立一個新串,不會修改原值(字串為常量)。

(5)Table構造式

  構造式是用於建立和初始化table的表示式。最簡單的構造式是{},用於建立一個空表。
  兩種常用的構造式:

  • 直接賦值。t={x=1,y=2};
  • 先初始為{},後賦值。t={},t.x=1,t.y=2;

  table作為陣列時,下表預設從1開始,方便與#t表長對應,但也可以顯示申明將第一個元素寫成t[0]。table結尾的逗號是可選的,中間的分號可用來表示不同的成分(分類)。
  示例1:建立連結串列

List = nil
For line in io.lines() do
	List={next=List,str=line}
End

4.   語句


(1)賦值

  Lua不同於其它語言,允許“多重賦值”。在多重賦值中,lua先對等號右邊的所有元素求值,然後才執行賦值,利用這項特性可快速交換元素,如:x,y=y,x,也常用於函式的返回值。
  賦值原則:若值的個數少於變數的個數,那麼多餘的變數湖北賦為nil;若值的個數更多的話,那麼多餘的值會被“靜悄悄地”丟棄掉。

(2)區域性變數和塊

  全域性變數一般直接使用,區域性變數用local作限定,其作用域僅限於宣告它們的那個塊。將外部變數賦值給區域性變數,可加速在作用域內對其的訪問,如:local fn = fn。一般在需要的地方宣告區域性變數,這樣可以縮短變數的作用域,且有助於提高程式碼的可讀性。
  一個塊是一個控制結構的執行體、或者是一個函式的執行體,再或者是一個程式塊。顯示宣告一個塊,只需將內容放在do … end之間。

(3)控制結構

if - then - else

  一個分支都是以end為結尾,不能出現多個end。lua中沒有switch語句,若要實現複雜的巢狀,可使用if - then - elseif - then - else - end結構。

while和repeat

  while和repeat最大的區別在於,前者先判斷條件,可能一次都不執行;後者先執行再判斷條件,至少可執行一次,repeat迴圈需要以until結尾。與其它語言不同的是,在lua中,一個宣告在迴圈體中的區域性變數的作用域包括了條件測試。

數字型for

  數字型for基本語法:for var=exp1,exp2,exp3 do…end。Var從exp1變化到exp3,每次變化以exp3為步長遞增,exp3是可選的,如果不指定預設為1。
  此外,控制變數會自動地宣告為for的區域性變數,如需跳出迴圈體,使用bread語句。

泛型for

  泛型for通過一個迭代器(iterator)函式來遍歷所有值。數字型for和泛型for,有兩個共同的特點:①迴圈變數是迴圈體的區域性變數②決不應該對區域性變數作任何賦值。
  lua標準庫中常用的幾種迭代器為:迭代檔案中每行的(io.lines)、迭代陣列元素的(ipairs)、迭代table元素的(pairs)、迭代字串中單詞的(gmatch)。具體語法如下:

  • for line in io.lines() do…end;
  • for i,v in ipairs(t) do…end;
  • for k,v in paris(t) do…end;
  • for v1,v2…vi in gmatch(s,pattern) do…end(gmatch可返回多個值,依託於()強制引數);

(4)模擬switch和continue

  Lua語言中是沒有switch和continue語句的,但是我們可以模擬出其功能。
  <1> 模擬switch,用函式和table來實現

Local switch = {
	[case1]=function(arg)...end,
	[case2]=function(arg)...end,etc
}

Switch[iCase](arg)

  <2> 模擬contionue,用do…end包住整個for塊

For k,v in pairs(t) do
	Do
		語句1
		Break;	--相當於continue的功能
		語句2
	End
End

5.  函式


  在lua中,函式是一種對語句和表示式進行抽象的主要機制。
  在面向物件式的函式呼叫中,提供:操作,可隱式傳入self引數,表示式o.foo(o,x)可改寫成o:foo(x)。
  在lua中,函式宣告時不能指定預設引數,但可以在函式體內通過“n = n or 1”或“t = t or {}”方式實現。

(1)多重返回值

  Lua具有一項非常與眾不同的特徵,允許函式返回多個結果,只需在return關鍵字後列出所有的返回值即可。
  函式雖然能返回多個返回值,但某些情況下只能獲取到部分值。將函式作為單獨一條語句時,丟棄所有返回值;如果函式呼叫不是一系列表示式的最後一個元素,那麼將只產生一個值。“一系列表示式”在Lua中表現為4種情況:多重賦值、函式呼叫時傳入的實參列表、table的構造式和return語句。詳細例項如下(假定函式f返回2個值):

  • x,y=f(),10;
  • print(f(),1);
  • t={f(),1};
  • return f(),1;

  如果將函式呼叫放在一對圓括號內,可強制其只返回一個結果,如(f())。
關於多重返回值,有一個特殊的函式——unpack,它接收一個數組作為引數。其功能是展開陣列中的所有元素(下標從1開始),unpack的一項重要用途體現在“泛型呼叫”,通用語法:f(unpack(a)),f為任意函式,只要陣列a取對應的引數值即可。
比如:

f = string.find;a={“hello”,“ll”}。

  示例1:用lua實現unpack函式

Function unpack(t,i)
	i = i or 1
	if t[i] then
		Return t[i],unpack(t,i+1)
	End
end

(2)變長引數

  在引數列表中,用3個點…表示變長引數,可當作table來解析(或先儲存)。具有變長的實參,函式呼叫實參對形參作初始化時,跟“多重賦值”類似。
  對於變長引數,如需獲取指定位置的元素,可使用select函式。此函式會處理值為nil的引數,原型為select(n,…),當n為數字時,返回第n個可變實參;當為字串“#”,則返回變長引數的長度。

(3)具名形參

  Lua中的引數傳遞機制是具有“位置性”的,也就是在呼叫一個函式時,實參是通過引數表中的位置取匹配形參的。
具名引數可防止引數書寫順序問題。當一個函式擁有大量的引數時,很容易把引數位置搞錯,將其引數列表打包table作為唯一引數傳遞是很有用的。
  例項1:檔案改名

Function rename(arg)
	Return os.rename(arg.old,arg.new)
End

Local arg={old=“1.lua”,new=“1.lua”}
Rename(arg)

  在設計一些介面的時候,可能有很多引數,但只有部分引數特別重要,此時可對原有函式進行封裝,內部呼叫時指定部分預設值,關鍵引數作校驗。這樣,使用者呼叫時只需關注部分引數,構造一個table即可,這也是一個很多的設計思路。

6.   深入函式


  Lua中有一個容易混淆的概念,當討論一個函式名時,實際上是在討論一個持有某函式的變數,因為函式是匿名的。
函式在Lua中屬於“第一類值”,不僅可以儲存在全域性變數中,還可以儲存在區域性變數甚至table的欄位中。所以,可以將函式應用於回撥、引數、table儲存等方面。

(1)closure(閉合函式)

  Lua詞法域:一個函式可以巢狀在另一個函式中,內部函式可以訪問外部函式中的變數。
按變數的作用域來分,除全域性變數和區域性變數外,還有一種“非區域性變數(外部變數)”,也就是closure中的區域性變數。一個closure就是一個函式加上該函式所需訪問的所有“非區域性的變數”。
  示例1:簡單計數

Function newCount()
	Local i=0
	Return function()
		i=i+1
		return i
	End
End

c1=newCount()
Print(c1) --結果1
Print(c1) --結果2
c2=newCount()
Print(c2) --新的closure,結果1
Print(c1) --持續累加,結果3

  示例2:函式重定義

Do
	Local sin=math.sin
	Math.sin=function(x)
		Reutrn sin(x*math.pi/180)
	End
End

  示例2中的奇妙之處在於,將老版本的sin儲存在一個私有變數中,並使用do…end程式塊限制sin的作用域。此後,只有新版本的sin才能訪問到它,外部呼叫math.sin只能訪問新版本的函式。
  使用這種技術,可以建立一個安全的執行環境,即所謂的“沙盒(sandbox)”。

(2)非全域性函式

  函式不僅可以作為全域性變數,還可以儲存在table的欄位和區域性變數中。典型的應用:面向物件和遞迴,如下:
  示例1:面向物件

Lib={}
Lib.foo=function(x,y) return x+y end
Lib.goo=function(x,y) return x-y end

  示例2:遞迴求階層

Local function fact(n)
If n == 0 then return 1
Else return n*fact(n-1) end
End

  對於一些間接的遞迴呼叫,需要提前將函式申明為區域性變數,如:

Local f,g
Function g() ... f() ... end
Function f() ... g() ... end

(3)尾呼叫消除

  Lua中函式中還有一個特性:尾呼叫消除,類似於goto語句,直接展開程式碼,不耗費任何棧空間。當一個函式呼叫另一個函式的最後一個動作時,該呼叫是一條“尾呼叫”,準確地說是“return ()”(也適用於複雜的表示式)。
  由於“尾呼叫”不會耗費棧空間,所以一個程式可以擁有無數巢狀的“尾呼叫”,且不會造成棧溢位。
  示例1:迷宮遊戲

Function room1()
	Local move = io.read()
	If move == “south” then return room2()
	Elseif move == “north” then return room3()
	Else
			print(“invaild romm”)
		Return room1()
	End
End
-- room2和room3實現類似
Function room4()
	Print(“congratulations”)
end