1. 程式人生 > >lua 學習筆記 一

lua 學習筆記 一

1.1
一個程式塊就是一連串的語句或命令
幾條連續的lua語句之間不需要分隔符,但用分號來分隔語句也是合法的。
退出lua直譯器互動模式使用os。exit();
使用-i引數啟動lua直譯器,直譯器會在執行完指定程式後進入互動模式


使用dofile執行程式塊
1,載入lua程式
2,呼叫lua函式


dofile("example.lua") --載入程式庫
val = function()  --呼叫程式庫的函式


1.2
Lua中的識別符號可以由任意字母,數字和下劃線構成,但不能以數字開頭。
lua的字母依賴於區域設定,但儘量不要使用區域的特定字母,避免在不支援特定字母的區域出錯。
lua中的保留字
and break do else elseif --注意之間是沒有空格的,和C/C++不同
in local nil not or
repeat return then true until
while


Lua區分大小寫,“And”不是保留字


lua的註釋方式:
-- 用於註釋一行
--[[ chunk ]]--  用於註釋一段程式碼


1.3
全域性變數不需要宣告,只需要將值賦予全域性變數即可建立。
訪問未初始化的變數不會引發錯誤,將返回一個特殊值nil。
刪除某個全域性變數,只需要用nil對其賦值。


1.4
可以通過對“_PROMPT”全域性變數賦值來修改互動模式的命令提示符
直譯器會為所有命令列引數建立一個名為“arg”的table,且指令碼名稱位於索引0上。


2.0 - 2.4
Lua是一種動態型別語言,值包含了自身的型別資訊。
lua中8中基礎型別 nil(空) boolean number string userdata(自定義型別) function thread table
函式type可以返回值的型別名稱,返回結果為字元型。
變數沒有預定義型別,任何變數可以包含任何型別的值
lua中,函式作為“第一類值”(first-class value)來看待,可以像操作其他值一樣來操作一個函式值。


Lua中字符采用8位編碼,lua字串中的字元可以具有任何數值編碼。字串是不可變的值(immutable values)。對字串的修改需要
建立一個新串接收修改後的值。 字串需要一對匹配的單引號或雙引號來界定。
在字串前放置操作符“#”可以獲得字串的長度,example:a = ‘123’ print(#a) -- 3


lua將false 和nil視為“假”,其他值都視為“真”


2.5-2.7
table的建立通過“構造表示式”來完成,最簡單的構造表示式{} 。
table永遠是“匿名的”(anonymous),一個持有table的變數和table自身之間沒有固定的關聯性。
table可以用不同型別的索引來訪問value,且在需要容納新條目時,能夠自動增長。
注意:a[x]和a["x"]是不同的,前者以變數x的值來索引,而後者以字串“x”來索引。
lua中可以用任何數字作為陣列索引的起始值,但對於陣列,通常以1作為索引的初始值。不少庫遵循這個慣例。
Lua將nil作為界定陣列結尾的標誌。
若陣列中有nil值,使用“#”操作符的結果不可預期,可以使用table.maxn來獲取陣列的最大正索引數。


userdata用於表示一種由應用程式或c語言庫所建立的新型別,沒有太多的預定義操作,只能進行賦值和相等性測試。


3.0 - 3.4
常規算術操作符:
二元:+ - * / ^(指數) % 
一元  -(負號)
所有操作符都可用於實數


關係操作符:
< > <= >= == ~=(不等於)
所有操作符的運算結果是true 或 false。
== ~=用於相等性和不相等性測試,nil只於其自身相等。對table,userdata和函式,只做引用比較。
“2”<"15"為true (按照字母次序來比較)


邏輯操作符:
and   or    not
and 若第一運算元為假,則返回第一運算元,否則返回第二運算元,or若第一運算元為真,返回第一運算元,否則返回第二運算元。
and 和 or 都使用了短路求值(short-cut evaluation)
(a and b)or c 類似於c語言中得a?b:c 其前提為b不能為假。


字串連線操作符
“ ..”     lua中字串是不可變值。


3.6
table 構造式用於建立和初始化table表示式。
列表風格 days = {“Sunday”,...,“Saturday”}   記錄風格 a = { x = 10,y = 20}
建立table之後,可以在table中建立或刪除其中的某些欄位。
Lua中很少用到連結串列,列表資料一般通過陣列來實現。
lua允許在方括號之間,顯式地用一個表示式來初始化索引值。 example i = 20; a = { [i+0] = 1,[i+1] = 2}
以0作為一個數組的起始索引 days = {[0]=“Sunday”,...,“Saturday”}


4.0 - 4.4
lua 允許多重賦值 example a,b = 10,20 -- a為10 ,b為20 在多重賦值中,Lua先對等號右邊的所有變數求值,然後執行賦值,因此交換兩個變數可以寫為:
x,y = y,x


多重賦值中,若值的個數少於變數的個數,則多餘的變數賦值為nil,若值的個數多,則多得值被丟棄。


通過loacl語句建立區域性變數 區域性變數的作用於僅限於宣告它的那個塊。一個塊是一個控制結構的執行體,或者一個函式的執行體,或者一個程式塊(chunk);
ps:互動模式中每行輸入內容自身就形成一個程式塊,導致區域性變數的使用出現非預期情況,可以通過do - end 來解決


使用區域性變數的優點: 一,可以減少命名衝突 ,二,訪問速度提高 三,作用域範圍有限,對記憶體的消耗減少。


if then else   while repeat-until  數字型for(numeric for)泛型for(generic for)
if then elseif then else


lua中,一個宣告在迴圈體中得區域性變數的作用域包括條件測試:example:
repeat
   local num = 0
   num = num + 1
   until num < 10  --在此處可以訪問num


for var = exp1,exp2,exp3 do
--執行程式碼
end
var從exp1 變換到exp2, 每次變換都以exp3作為步長遞增var,並執行一次執行程式碼,exp3 預設為1.若不想為迴圈設定上限,則將exp2 設為常量math.huge。


泛型for通過一個迭代器(iterator)函式來遍歷所有值。常用迭代器有迭代檔案 io.lines,迭代table元素 pairs, 迭代陣列元素 ipairs, 迭代字串單詞 
string.gmatch 等,也可以自己編寫迭代器。
對for迴圈, 1,迴圈變數是迴圈體的區域性變數, 2,決不對迴圈變數作任何賦值。


break 或return只能是一個塊的最後一條語句


5.0 -5.3
函式的所有引數必須放到一對圓括號中,但如果一個函式只有一個引數,且引數是一個字面字串或者table構造式,則圓括號可有可無。
冒號操作符 : 冒號操作符使呼叫函式時將呼叫者作為函式的隱含第一引數,example o.foo(o,x) 也可寫成 o:foo(x).
函式的形參和實參的個數匹配會自動調整。以匹配引數表的要求


多返回值函式
lua會調整一個函式的返回值數量以適用不同的呼叫情況,作為單獨語句時,捨棄所有返回值,作為表示式一部分時,返回第一個返回值,當函式是一系列表示式中的最後一個
時(或僅有一個元素),返回全部值。
一系列表示式表現為:多重賦值, 函式實參列表,table構造式,return語句。
lua中函式可以接受不同數量的實參,即變長引數函式。引數列表中的“ ... ”表示函式可以接受不同數量的實參。讀取引數時, ... 表示一個由所有變長引數構成的陣列。example format函式


6.0 - 6.3
具名實參,實參通過它在引數表中得位置於形參匹配起來,適用table作為函式的引數來實現具名。
函式與其他值一樣是匿名的,討論一個函式名,實際是討論一個持有某函式的變數。


closure(閉合函式) 一個函式寫在另一個函式之內,從而使內部的函式可以訪問外部函式中的區域性變數。
外部函式的區域性變數,對內部函式來說,既不是全域性變數也不是區域性變數,稱為“非區域性的變數(non-local variable)”
一個closure就是一個函式加上該函式所需訪問的所有“非區域性變數”。通過不同的變數訪問函式,將得到一個新的closure。
example
function newCounter ()
local i = 0
return function ()
i = i + 1
return i
end
end


c1 = newCounter() -- 注意後面跟有括號,
c2 = newCounter() 


則c1和c2 是同一函式建立的兩個不同closure.


在遞迴中使用區域性函式時,需要注意在區域性函式中呼叫區域性函式自身將不能實現。
lua展開區域性函式定義的“語法糖”,使用的是區域性函式定義:
local foo
foo = function (<引數>)<函式體> end


對於間接遞迴,必須使用一個明確的前向宣告(forward declaration)
proper tail call lua支援“尾呼叫消除”(tail-call elimination)
當一個函式呼叫是另一個函式的最後一個行為時,稱該呼叫為“尾呼叫”
function f(x) return g(x) end
在呼叫“尾呼叫”後,程式不再需要保持任何關於該函式的棧資訊,當g返回時,執行控制權直接返回到呼叫f的地方,使得呼叫g時,f不再耗費任何棧空間.
因此,採用尾呼叫,將不會導致出現棧溢位的情況。
在lua中,只有 “return <func>(<args>)”的形式才是”尾呼叫“


7.0 - 7.5
迭代器是一種可以遍歷集合中所有元素的機制,在lua中,通常將迭代器表示為函式。可以採用closure來實現。
單純的closure實現存在需要為每個for迴圈建立一個closure的問題。


泛型for在迴圈過程內部儲存了迭代器函式,包括3個值 1,一個迭代器函式 2,一個恆定狀態, 3,一個控制變數
for <var-list> in <exp-list> do
<body>
end
無狀態迭代器不儲存自身任何狀態,從而可以在多個迴圈中使用同一個無狀態迭代器,避免建立新closure的開銷
for迴圈使用恆定狀態和控制變數來呼叫迭代器,並生產下次迭代的元素
local function iter (a , i)
i = i + 1
local v = a[i]
if v then
return i , v
end
end
function ipairs (a)
return iter,a ,0
end 


for i,v in ipairs(a) do
print(i,v)
end
pairs會以table中得任意次序返回一組值,其迭代器函式是lua中的next。
具有複雜狀態的迭代器,可以使用closure,也可以將迭代器所需的狀態打包為一個table,儲存在恆定狀態中。
table中的資料可以修改,但在迴圈過程中恆定總是同一個table。
在效能上,無狀態迭代器優於closure實現的迭代器,closure實現的迭代器優於table實現的迭代器。


8.0 - 8.5
loadfile會從一個檔案載入lua程式碼塊,並編譯程式碼,並將編譯的結果作為一個函式返回,同時,該函式不會引發錯誤,它返回錯誤但不處理錯誤。
loadfile loadstring(開銷比較大) 二個函式的返回值是一個函式。
loadstring總是在全域性環境中編譯他的字串。 如果需要對loadstring函式的引數表示式求值,則引數前需要加上return。
load接收一個“讀取器函式”,並在內部呼叫它來獲取程式塊,一般只在程式塊不再檔案中,或者程式塊過大無法放入記憶體時使用。
Lua將所有獨立的程式塊視為一個匿名函式的函式體,且該匿名函式具有可變長實參。
load函式不會引發錯誤,在錯誤時,load會返回nil及一條錯誤訊息。
lua中,函式定義是一種賦值操作,是在執行時才完成的操作。


檢查平臺是否支援動態連結機制, print(package.loadlib("a","b"))然後觀察執行結果。
loadlib函式載入指定的庫,並將其連結如lua。
require函式會搜尋指定的庫,再使用loadlib來載入庫,並返回初始化函式。初始化函式將庫中提供的函式註冊到lua中。
assert函式檢查第一個引數是否為true,為true,則返回該引數,若為false,則引發一個錯誤。
若需要在lua中處理錯誤,使用函式pcall來包裝要執行的程式碼。
當遇到內部錯誤時,lua會產生錯誤訊息,其他時候,錯誤訊息就是傳遞給error函式的值。
error函式的第二個附加引數level,用於支出應由呼叫層級中得那個層來報告當前錯誤。
xpcall函式第二個引數錯誤處理函式,可以用來獲取呼叫棧的資訊。debug.traceback。


9.0 - 9.1
一個具有多個協同程式的程式在任意時刻只能執行一個協同程式,並且正在執行的協同程式只會在顯式的要求掛起(suspend)時,它的執行才會暫停。
Lua將所有關於協同程式的函式放置在一個名為“coroutine”的table中,函式create用於建立新的協同程式。
協同程式有4種不同的狀態:掛起(suspend),執行(running),死亡(dead),正常(normal)。
當建立一個協同程式時,它處於掛起狀態,可以通過函式status來檢查協同程式的狀態。
從協同程式的角度看,所有在它掛起時發生的活動都發生在yield呼叫中,當恢復協同程式的執行時,對yield的呼叫才最終返回。
resume是在保護模式下執行的,如果一個協同程式的執行中發生任何錯誤,lua不會顯示錯誤訊息,而是將執行權返回給resume函式。
當協同程式a喚醒協同程式b時,協同程式a既不是掛起,也不是執行狀態,這時的狀態稱為“正常”。
通過resume-yield來交換資料:
第一次呼叫resume時,並沒有對應的yield在等待它,因此所有傳遞給resume的額外引數都將視為協同程式主函式的引數。
co = coroutine.create( function (a,b,c)
print ("co",a,b,c)
end)
coroutine.resume(co,1,2,3) -- co 1 2 3
對resume呼叫返回的內容,第一個值true表示沒有錯誤,後面的值是對應yield傳入的引數
co = coroutine.create ( function (a,b)
coroutine.yield(a + b, a - b)
end)
print(coroutine.resume(co,20,10)) -- true 30 10
與此對應,yield返回的額外值就是對應resume傳入的引數:
co = coroutine.create (function ()
print("co",coroutine.yield())
end)
coroutine.resume(co)
coroutine.resume(co,4,5) -- co 4 5
最後一個協同程式結束時,它的主函式返回的值都將作為對應resume的返回值:
co = coroutine.create(function
return 6,7
end)
print(coroutine.resume(co)) -- true 6 7


非對稱協同程式(semi-coroutine)存在兩個函式來控制協同程式的執行,一個用於掛起,一個恢復執行。