1.1程序塊:Lua執行的每段代碼,例如一個源代碼文件或者交互模式中輸入的一行代碼,都稱為一個程序塊
1.2注釋:- - or --[[ --]]
1.3全局變量:無需聲明,只需將值賦予一個全局變量就可以創建了。無需刪除,若要刪除直接賦值nil。
1.4解釋器程序
選項參數“-e”可以直接在命令行中輸入代碼。
選項參數“-l”用于加載庫文件。
選項參數“-i”表示在運行完其他命令行參數后進入交互模式
只要定義了一個名為“_PROMPT”的全局變量。解釋器就會用它的值作為交互模式的命令提示符。例如
CXQ-MacBook-Pro:LUA-learn chenxiaoqiang$ lua -i -e "_PROMPT = 'lua>'"Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio
lua>//提示符
類型與值
Lua動態類型的語言。無類型定義的語法。
總共八種數據類型:nil表示“無效值”
boolean:true or false
number:表示的十實數
string字符串:Lua字符串是不可變的值。不能像C語言一樣直接修改字符串的某個字符,而是應該根據修改要求來創建一個新的字符串。
a = “one string"
b= string.gsub(a, “one”, “anther")
“..”是Lua字符串連續操作符。數字后面記得先空格,不然會默認為小數點
例如:print(10 .. 20)
盡量不要使用這種強制轉換。 而應該使用print(tostring(10)).
table:對象,永遠是“匿名的”,一個持有table的變量與table自身之間沒有固定的關聯性。
lua>a = {}lua>k = "x"lua>a[k] = 100
lua>a[20] = "great"
lua>b=a
print(b[“x”]) —->100 等同于b.x
a = nil
b = nil
Lua中數組通常以1作為索引的起始值
Lua中長度操作符“#“用于返回一個數組或線性表的最后一個索引值(或為其大?。?br> — - 打印所有行
for i=1, #a do
print(a[i])
end
print(a[#a]) - -打印列表a的最后一個值
a[#a] = nil --刪除最后一個值
a[#a+1] = v - -將v添加到列表末尾
user data(自定義類型),function,thread.
print(type(“hello world"))
表達式
table構造式
空構造式{}
days = {“Sunday”,”Monday”} days[1] = “Sunday”….
lua>polyline = {color = "blue", thickness = 2, npoints = 4,
{x=0,y=0},{x=-10,y=0}}
print(polyline[2].x) - - -10
構造式{x = 0, y = 0}相當于{[“x”]=0,[“y”]=0}
構造式{“r”,”g”,”b”}相當于{[1]= “r”,[2]=“g”,[3]=“b"}
語句
賦值:允許多重賦值,即一下子將多個值賦予多個變量
lua>a,b = 20,5*x
多重賦值:左邊大于右邊個數為nil, 反之則舍棄
局部變量:local語句來創建
局部變量只限于在塊中使用。
一個塊block:是一個控制結構的執行體,或者是一個函數的執行體再或者是一個程序塊。
盡可能的使用局部變量是一種良好的編程風格
控制結構:
if then else end or elseif
while 條件 do
end
repeat-until 語句重復執行其循環體直到條件為真時結束。
在Lua中,一個聲明在循環體中得局部變量的作用域包括了條件測試
數字型for : for var = exp1,exp2,exp3 do
end
var從exp1變化到exp2,每次步長為exp3。
如果不想有上限則使用math.huge
泛型for: 循環通過一個迭代器(iterator)函數來遍歷所有值
打印a的所有值
for i,v in ipairs(a) do print(v) end
Lua的基礎庫提供了ipairs,這是一個用于遍歷數組的迭代器函數。
i會被賦予一個索引值,v被賦予一個對應于索引值的數組元素值
函數
多重返回值:Lua允許函數返回多個結果
unpack函數。接受一個數組作為參數,并從下標1開始返回該數組的所有元素
function unpack(t,i)>> i = i or 1>> if t[i] then>> return t[i],unpack(t, i + 1)>> end
end
變長函數
function add(...)
local s = 0
for i,v in ipairs(...) do
s= s + v
end
return s
…表示函數可接受不同數量的實參
function fwrite(fmt, ...)>> return io.write(string.format(fmt, ...))>> end
fwrite("%d%d",4,5)
格式化文本string.format
一個函數在遍歷其變長參數時只需使用表達式{…}。如果變長參數中包含一些故意傳入的nil,那么此時就需要用函數select來訪問變長參數了。調用select的時候必須傳入一個固定參數selector和一系列變長參數。調用selector為數字N,那么select返回它的第n個可變實參;否則,selector只能為字符串”#“,這樣select會返回變長參數的總數
具名實參
深入函數
在Lua中。函數是一種”“第一類值”,它們具有特定的詞法域
第一類值:表示在Lua中函數與其他傳統的類型的值(例如數字和字符串)具有相同的權利。函數可以存儲到變量中無論是全局或者是局部?;騮able中,可以作為實參傳遞給其他函數,還可以作為其他函數的返回值
詞法域:一個函數可以嵌套在另一個函數中,內部的函數可以訪問外部函數中的變量。
函數與所有其他值一樣都是匿名的。即它們都沒有名稱。當討論一個函數名時(例如print),實際上是在討論一個持有某函數的變量。這與其他變量持有各種值是一個道理。
a = {p = print}
a.p("hello")
hello
function foo (x) return 2x end
等同于 foo = function (x) 2x end
一個函數定義實際就是一條語句。
閉合函數closure
function sortgrade(names,grades) table.sort(names, function(n1,n2)>> return grades[n1] > grades[n2]>> end)
end
grades稱為“非局部的變量”。既不是全局也不是局部。但sort中得匿名函數卻可以訪問
function newCounter()>> local i = 0>> return function()>> i = i + 1>> return i>> end
end
c1 = newCounter()
c2 = newCounter()
一個closure就是一個函數加上該函數所需訪問的所有“非局部的變量”。如果再次調用newCounter,那么它會創建一個新的局部變量i,從而得到一個新的closure.
closure對于回調函數也很有用。
非全局的函數
Lib = {}> Lib.foo = function (x,y) return x+y end
Lib.goo = function (x,y) return xy end
上下實現是相同的
function Lib.foo (x,y) return x+y end
function Lib.goo (x,y) return xy end
定義遞歸的局部函數時,有特別需要注意的地方。先定義一個局部變量,然后再定義函數本身
local fact
fact = function(n) - - 若是local fact = function(n)會有錯誤,定義未完畢
if n == 0 then return 1
else return n*fact(n-1)
end
end
正確地尾調用
Lua支持“尾調用消除”:尾調用(tail call)就是一種類似于goto的函數調用。當一個函數調用是另一個函數的最后一個動作時,該調用才算是一條“尾調用”。舉例:
function f(x) return g(x) end
也就是說當f調用完g后就再無其他事情可做了。(尾調用不消耗??臻g)
不符合尾調用的一些情況
return g(x) + 1 必須做一次加法
return x or g(x)
return (g(x)) 必須調整為一個返回值
在Lua 中,只有return <func>(<args>)這樣的調用形式才算是一條尾調用
尾調用的一大應用就是編寫“狀態機”。這種程序通常以一個函數來表示一個的狀態,改變狀態就是goto到另一個特定的函數。
迭代器與泛型for
迭代器與closure
迭代器:就是一種可以遍歷一種集合中所有元素的機制。在Lua中通常將迭代器表示為函數,每調用一次函數,即返回集合中的“下一個”元素
一個closure結構通常涉及到兩個函數:closure本身和一個用于創建該closure的工廠函數。
例子:
function values(t)>> local i = 0>> return function () i = i+1; return t[i] end>> end> t = {10,20,30}> iter = values(t)> while true do>> local element = iter()>> if element == nil then break end>> print(element)
end
泛型for
for element in values(t) do>> print(element)
end
i = i + 1>> return i>> end
>> end
> c1 = newCounter()
> c2 = newCounter()
一個closure就是一個函數加上該函數所需訪問的所有“非局部的變量”。如果再次調用newCounter,那么它會創建一個新的局部變量i,從而得到一個新的closure.
closure對于回調函數也很有用。
非全局的函數
> Lib = {}> Lib.foo = function (x,y) return x+y end
> Lib.goo = function (x,y) return xy end
上下實現是相同的
> function Lib.foo (x,y) return x+y end
> function Lib.goo (x,y) return xy end
定義遞歸的局部函數時,有特別需要注意的地方。先定義一個局部變量,然后再定義函數本身
local fact
fact = function(n) - - 若是local fact = function(n)會有錯誤,定義未完畢
if n == 0 then return 1
else return n*fact(n-1)
end
end
正確地尾調用
Lua支持“尾調用消除”:尾調用(tail call)就是一種類似于goto的函數調用。當一個函數調用是另一個函數的最后一個動作時,該調用才算是一條“尾調用”。舉例:
function f(x) return g(x) end
也就是說當f調用完g后就再無其他事情可做了。(尾調用不消耗??臻g)
不符合尾調用的一些情況
return g(x) + 1 必須做一次加法
return x or g(x)
return (g(x)) 必須調整為一個返回值
在Lua 中,只有return <func>(<args>)這樣的調用形式才算是一條尾調用
尾調用的一大應用就是編寫“狀態機”。這種程序通常以一個函數來表示一個的狀態,改變狀態就是goto到另一個特定的函數。
迭代器與泛型for
迭代器與closure
迭代器:就是一種可以遍歷一種集合中所有元素的機制。在Lua中通常將迭代器表示為函數,每調用一次函數,即返回集合中的“下一個”元素
一個closure結構通常涉及到兩個函數:closure本身和一個用于創建該closure的工廠函數。
例子:
> function values(t)>> local i = 0>> return function () i = i+1; return t[i] end>> end> t = {10,20,30}> iter = values(t)> while true do>> local element = iter()>> if element == nil then break end>> print(element)
>> end
泛型for
> for element in values(t) do>> print(element)
>> end
泛型for的語法:for <var-list> in <exp-list> do
<body>
end
其中<var-list>是一個或多個變量名的列表,以逗號分隔;<exp-list>是一個或多個表達式的列表,同樣以逗號分隔。通常表達式列表只有一個元素,即對迭代器工廠的調用。例如
for k,v in pairs(t) do print(k,v) end
變量列表的第一元素稱為“控制變量”。在循環過程中該值絕不會是nil
for做的第一件事就是對in后面的表達式求值。這些表達式應該返回3個值供for保存:
迭代器函數、恒定狀態和控制變量 的初值。這里類似于多重賦值,即只有最后一個表達式才會產生多個結果,并且只會保留前3個值,多余的值會被丟棄;而不足的話將以nil補足。
無狀態的迭代器
就是一種自身不保存任何狀態的迭代器。因此,我們可以在多個循環中使用同一個無狀態的迭代器,避免創建新的closure開銷。
在每次迭代中,for循環都會用恒定狀態和控制變量來調用迭代器函數,一個無狀態的迭代器可以根據這兩個值來為下次迭代生成一個元素。例如ipairs。
具有復雜狀態的迭代器
local iterator> function allwords()>> local state = {line = io.read(), pos = 1}>> return iterator, state>> end> function iterator(state)>> while state.line do>> local s,e = string.find(state.line, "%w+", state.pos)>> if s then>> state.pos = e + 1>> return string.sub(state.line, s, e)>> else>> state.line = io.read()>> state.pos = 1>> end>> end>> return nil
end
協同程序
概念:協同程序與線程差不多,也就是一條執行序列,擁有自己獨立的棧、局部變量和指令指針,同時又與其他系統程序共享全局變量和其他大部分的東西。
一個具有多個協同程序的程序在任意時刻只能運行一個協同程序。并且正在運行的協同程序只會在其顯式地要求掛起時,它的執行才會暫停。
1.基礎知識:
協同程序的函數放置在一個名為“coroutine”的table中。
四個狀態:掛起、運行、死亡、正常。
coroutine.create() coroutine.resume() coroutine.status() coroutine.yield()
協同程序的真正強大之處在于函數yield的使用上,該函數可以讓一個運行中的協同程序掛起,而之后可以再恢復它的運行。
Lua>co = coroutine.create(function ()>> for i=1,10 do>> print("co",i)>> coroutine.yield()>> end
end)
在resume,并沒有對應的yield在等待它,因此所有傳遞給resume的額外參數都將視為協同程序的主函數的參數。
Lua>co = coroutine.create(function (a,b,c) print("co",a,b,c) end)
Lua>return coroutine.resume(co,1,2,3)
在resume調用返回的內容中,第一個值為true則表示沒錯,而后面所有的值都是對yield傳入的參數:
Lua>co = coroutine.create(function (a,b)>> coroutine.yield(a+b,a-b)>> end)
Lua>coroutine.resume(co,10,9)
與此對應的是,yield的返回的額外值就是對應resume傳入的參數:
print("co",coroutine.yield())
end)
return iterator, state>> end> function iterator(state)>> while state.line do>> local s,e = string.find(state.line, "%w+", state.pos)>> if s then>> state.pos = e + 1>> return string.sub(state.line, s, e)>> else>> state.line = io.read()>> state.pos = 1>> end>> end>> return nil
>> end
協同程序
概念:協同程序與線程差不多,也就是一條執行序列,擁有自己獨立的棧、局部變量和指令指針,同時又與其他系統程序共享全局變量和其他大部分的東西。
一個具有多個協同程序的程序在任意時刻只能運行一個協同程序。并且正在運行的協同程序只會在其顯式地要求掛起時,它的執行才會暫停。
1.基礎知識:
協同程序的函數放置在一個名為“coroutine”的table中。
四個狀態:掛起、運行、死亡、正常。
coroutine.create() coroutine.resume() coroutine.status() coroutine.yield()
協同程序的真正強大之處在于函數yield的使用上,該函數可以讓一個運行中的協同程序掛起,而之后可以再恢復它的運行。
Lua>co = coroutine.create(function ()>> for i=1,10 do>> print("co",i)>> coroutine.yield()>> end
>> end)
在resume,并沒有對應的yield在等待它,因此所有傳遞給resume的額外參數都將視為協同程序的主函數的參數。
Lua>co = coroutine.create(function (a,b,c) print("co",a,b,c) end)
Lua>return coroutine.resume(co,1,2,3)
在resume調用返回的內容中,第一個值為true則表示沒錯,而后面所有的值都是對yield傳入的參數:
Lua>co = coroutine.create(function (a,b)>> coroutine.yield(a+b,a-b)>> end)
Lua>coroutine.resume(co,10,9)
與此對應的是,yield的返回的額外值就是對應resume傳入的參數:
>> print("co",coroutine.yield())
>> end)
Lua>coroutine.resume(co)trueLua>return coroutine.resume(co,1,2)
co 1 2
最后,當一個協同程序結束時,它的主函數所返回的值都將作為對應resume的返回值。
Lua>co = coroutine.create(function()>> return 6,7>> end)
Lua>print(coroutine.resume(co))
true 6 7
- 管道(pipe)與過濾器(filter)
協同程序經典案例“生產者-消費者”
Lua>function receive(prod)>> local status, value = coroutine.resume(prod)>> return value>> endLua>function send(x)>> coroutine.yield(x)>> endLua>function producer()>> return coroutine.create(function ()>> while true do>> local x = io.read()>> send(x)>> end>> end)>> endLua>function filter(prod)>> return coroutine.create(function()>> for line = 1, math.huge do>> local x = receive(prod)>> x = string.format(%5d %s",line,x)stdin:5: unexpected symbol near '%'
Lua>function filter(prod)
return coroutine.create(function()
for line = 1, math.huge do
local x = receive(prod)
x = string.format("%5d %s",line,x)
send(x)>> end>> end)>> endLua>function consumer (prod)>> while true do>> local x = receive(prod)>> io.write(x, "\n")>> end>> endLua>p = producer()Lua>f = filter(p)
Lua>consumer(f)
以協同程序實現迭代器
非搶占式的多線程
數據結構
1.數組
a = {} 新建
for i=1, 1000 do a[i] = 0
end
mt = {}
for i=1,N do mt[i] = {} --創建一個新行 for j=1,M do mt[i][j] = 0 end
Lua中table是一種對象,因此在創建矩陣時,必須顯示地創建每一行。
元表(metatable)與元方法(meatmethod)
通常,Lua中得每個值都有一套預定義的操作集合。例如,數字相加,連接字符串。但是無法相加兩個table,無法對函數比較,也無法調用一個字符串。
可以通過元表來修改一個值得行為,使其在面對一個非預定義的操作時執行一個指定的操作。
例如定義如何相加table的表達式。
Lua試圖相加兩個table時,它會先檢查兩者之一是否有元表,然后檢查該元表中是否有一個叫_add的字段。如果Lua找到了該字段,就調用該字段對應的值。這個值也就是所謂的“元方法”,它應該是一個函數。
Lua中得每個值都有一個元表。Lua在創建新的table時不會創建元表:
t = {} print(getmetatable(t)) - - nil
使用setmetatable來設置或修改任何table的元表:
t = {}
t1 = {}
setmetatable(t, t1)
assert(getmetatable(t) == t1)
任何table都可以作為任何值的元表,而一組相關的table也可以共享一個通用的元表,此元表描述了它們的共同行為,一個table甚至可以作為它自己的元表,用于描述其特有的行為。
在Lua中只能設置table的元表。其他需要通過C代碼來完成。
算術類的元方法
Set = {}
local mt = {}
function Set.new(l)
local set = {}
setmetatable(set, mt)
for _,v in ipairs(l) do
set[v] = true
end
return set
end
function Set.union(a,b)
local res = Set.new{}
for k in pairs(a) do
res[k] = true
end
for k in pairs(b) do
res[k] = true
end
return res
end
function Set.intersection (a,b)
local res = Set.new{}
for k in pairs(a) do
res[k] = b[k]
end
return res
end
function Set.tostring(set)
local l = {}
for e in pairs(set) do
l[#l + 1] = e
end
return "{" .. table.concat(l, ", ") .. "}"
end
function Set.print(s)
print(Set.tostring(s))
end
s1 = Set.new{10,20,30,40}
s2 = Set.new{30,1}
mt.__add = Set.union
s3 = s1 + s2
Set.print(s3)
mt.__mul = Set.intersection
Set.print((s1+s2)*s1)
如果想要清楚得到錯誤信息,則必須在實際操作前顯示地檢查操作數的類型
關系類的元方法
table訪問的元方法
有兩種可以改變table的行為:查詢table及修改table中不存在的字段
_ _index元方法:如果有此方法當訪問查詢不到的時候會調用此方法(繼承)table的更新
元方法可以是函數也可以是另一個table
_ newindex:用于table的更新。功能相似
兩者的組合可以實現Lua的一些強大的功能:只讀的table,默認值的table和面向對象編程
1.默認值的table
function setDefault (t,d)
local mt = { _index = function () return d end}
setmetatable(t,mt)
end
環境
Lua將其所有的全局變量保存在一個常規的table中,這個table稱為“環境(environment)”
優點:其一,不需要再為全局變量創造一種新的數據結構,因此簡化了Lua的內部實習。另外一個優點是,可以像其他table一樣操作這個table。
為了便于實時這種操作,Lua將環境table自身保存在一個全局變量_G中。以下代碼打印當前環境所有全局變量的名稱:
for n in pairs(_G) do print(n) end
全局變量聲明:
value = _G["varname"]> valuex> _G["test"] = value
test
非全局的環境
setfenv(1,{})將當前環境改為一個新的空table
1表示當前函數,2表示調用當前函數的函數
模塊與包
1.require函數
即使知道某些用到的模塊可能已經加載了,但只要用到require就是一個良好的編程習慣。
2.編寫模塊的基本方法
最簡單的方法:創建一個table
3.使用環境
創建模塊的基本方法的缺陷在于,它要求程序員投入一些額外的關注。當訪問同一模塊中的其他公共實體時,必須限定其名稱。并且,只要一個函數的狀態從私有變為公有(反之也可),就必須修改調用。
函數環境可以解決上述創建模塊時遇到的問題
面向對象編程
Lua的table是一種對象,首先:table與對象一樣可以擁有狀態,其次,table也與對象一樣擁有一個獨立于其值得標識(一個self)。最后table與對象一樣具有獨立于創建者和創建地的生命周期。
self的使用
Account = {balance = 0}> function Account.withdraw(v)>> Account.balance = Account.balance - v>> end
Account.withdraw(100.0)
Account = nil
a.withdraw(0) --錯誤
通過一個額外的參數self or this修改。
Account = {balance = 0}> function Account.withdraw(self,v)>> self.balance = self.balance - v>> return self.balance>> end
a = Account
return a.withdraw(a,100)
在Lua中。只需要使用冒號,就可以隱藏self參數
function Account:withdraw(v)>> self.balance = self.balance - vend
a = Account
Account = nil
return a:withdraw(100)
1.類:
在Lua中實現原型很簡單,使用繼承即可。更準確地說,如果有兩個對象a和b,要讓b作為a的一個原型,只需輸入下面語句
setmetable(a,__index = b)
在此之后,a就會在b中查找所有它沒有的操作,若將b稱為是對象a的類,只不過是術語上的一個變化而已。
Account = {balance = 0}
function Account:new(o)
o = o or {} setmetatable(o,self) self.__index = self
return o
end
當調用Account:new的時候,self就等于Account
2.繼承
由于類也是對象,它們也可以從其他類獲得方法。這種行為就是一種繼承,可以很容易地在Lua中。
Account = {balance = 0}
function Account:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function Account:withdraw(v)
if self.balance < v then print("insufficient funds") end
self.balance = self.balance - v
end
function Account:deposit(v)
self.balance = self.balance + v
end
--派生子類使得賬戶可以透支
SpecialAccount = Account:new()
s = SpecialAccount:new{limit = 1000.0}
function SpecialAccount:withdraw(v)
if v - self.balance >= self:getLimit() then
print("insufficient funds")
end
self.balance = self.balance - v
end
function SpecialAccount:getLimit()
return self.limit or 0
end
test = SpecialAccount:new()
test:withdraw(500)
print(test.balance)
3.多重繼承
弱引用table
三種方式:弱引用table,弱引用key,弱引用兩者
一個table的弱引用類型是通過其元表中得—mode字段來決定的。這個字段的值應為一個字符串,如果這個字符串包含字母’k’,那么這個table的key是弱引用;如果這個字符串包含字母’v’,那么這個table是value的弱引用,
a = {}b= {__mode = "k"}setmetatable(a,b) --'a'為key的弱引用key = {}a[key] = 1key = {}a[key] = 2collectgarbage()for k,v in pairs(a) do print(v)
end
備忘錄
local results = {}
setmetateble(results,{__mode = "v"})function createRGB(r,g,b) local key = r .. "-" .. g "-" .. b local color = results(key) if color == nil then color = {red = r, green = g, blue = b} results[key] = color end return color
end