首先調用迭代工廠;內部保留迭代函數,所以咱們不須要 iter 變量;而後在每個新的迭代處調用迭代器函數;當迭代器返回 nil 時循環結束。
在循環過程當中範性 for 在本身內部保存迭代函數,實際上它保存三個值:迭代函數、狀態常量、控制變量。程序員
範性 for 的執行過程:閉包
具體舉例:函數
for var_1, ..., var_n in explist do block end -- 等價於 do local _f, _s, _var = explist while true do local var_1, ... , var_n = _f(_s, _var) _var = var_1 if _var == nil then break end block end end
無狀態的迭代器是指不保留任何狀態的迭代器,所以在循環中咱們能夠利用無狀態迭代器避免建立閉包花費額外的代價。
每一次迭代,迭代函數都是用兩個變量(狀態常量和控制變量)的值做爲參數被調用,一個無狀態的迭代器只利用這兩個值能夠獲取下一個元素。學習
本身實現ipairs舉例:this
a = {"one", "two", "three"} 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
Lua 庫中實現的 pairs 是一個用 next 實現的原始方法:lua
function pairs (t) return next, t, nil end for k, v in next, t do ... end
不少狀況下,迭代器須要保存多個狀態信息而不是簡單的狀態常量和控制變量,最簡單的方法是使用閉包,還有一種方法就是將全部的狀態信息封裝到 table 內,將 table做爲迭代器的狀態常量,由於這種狀況下能夠將全部的信息存放在 table 內,因此迭代函數一般不須要第二個參數。prototype
咱們應該儘量的寫無狀態的迭代器,由於這樣循環的時候由 for 來保存狀態,不須要建立對象花費的代價小;若是不能用無狀態的迭代器實現,應儘量使用閉包;儘量不要使用 table 這種方式,由於建立閉包的代價要比建立 table 小,另外 Lua 處理閉包要比處理 table 速度快些。後面咱們還將看到另外一種使用協同來建立迭代器的方式,這種方式功能更強但更復雜。代理
多狀態迭代器舉例:code
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 for i in allwords() do print(i) end --function allwords(f) -- for l in io.lines() do -- for w in string.gfind(l, "%w+") do -- f(w) -- end -- -- end -- --end --allwords(print)
舉例:orm
list=nil -- 鏈表最末尾節點 list={next=list,value=v} -- 鏈表建立時往前賦值 local l=list while l do -- 遍歷鏈表 print(l.value) l=l.next end
舉例:
List={} function List.new() return {first=0,last=-1} end function List.pushleft(list,value) local first=list.first-1 list.first=first list[first]=value end function List.pushright(list,value) local last=list.last+1 list.last=last list[last]=value end function List.popleft(list) local first=list.first if first>list.last then error("list is empty") end local value = list[first] list[first]=nil list.first=first+1 return value end function List.popright(list) local last=list.last if last < list.first then error("list is empty") end local value=list[last] list[last]=nil list.last=list.last-1 return value end local a =List.new() List.pushright(a,1) List.pushright(a,2) List.pushright(a,3) List.pushright(a,4) print(List.popright(a)) -- 4 print(List.popright(a)) -- 3 print(List.popright(a)) -- 2 print(List.popright(a)) -- 1
舉例:
function Set(list) local set={} for _,l in ipairs(list) do set[l]=true end return set end reserved = Set{"while", "end", "function", "local", "local","while"} -- 已經將table中的元素去重了 for k,v in pairs(reserved)do io.write(k," ") end -- local function end while
遞歸實現table的序列化。
舉例:
function serialize(o) if type(o)=="number" then -- 若是是number則直接寫入 io.write(o) elseif type(o)=="string" then -- 若是是string經過format寫入,用%q可使用雙引號表示字符串而且能夠正確的處理包含引號和換行等特殊字符的字符串 io.write(string.format("%q",o)) elseif type(o)=="table" then -- 若是是table則遍歷table中的元素,遞歸調用serialize io.write("{\n") for k,v in pairs(o) do -- io.write(" ",k," = ") io.write("[") serialize(k) io.write("] = ") serialize(v) io.write(",\n") end io.write("}\n") else error("can not serialize a " .. type(o)) end end a={a="1",b="2",c={1,2,3,4,5},"q","w","e","r","t","y","u" } serialize(a) -- { -- [1] = "q", -- [2] = "w", -- [3] = "e", -- [4] = "r", -- [5] = "t", -- [6] = "y", -- [7] = "u", -- ["b"] = "2", -- ["c"] = { -- [1] = 1, -- [2] = 2, -- [3] = 3, -- [4] = 4, -- [5] = 5, -- } -- , -- ["a"] = "1", -- }
Metatables 容許咱們改變 table 的行爲,例如,使用 Metatables 咱們能夠定義 Lua 如何計算兩個 table 的相加操做 a+b。當 Lua 試圖對兩個表進行相加時,他會檢查兩個表是否有一個表有 Metatable,而且檢查 Metatable 是否有__add 域。若是找到則調用這個__add函數(所謂的 Metamethod)去計算結果。
舉例:
Set={} Set.mt={} function Set.new(t) local set={} setmetatable(set,Set.mt) for _,l in ipairs(t) do set[l]=true end return set end function Set.union(a,b) if getmetatable(a)~=Set.mt or getmetatable(b)~=Set.mt then error("attempt to `add' a set with a non-set value",2) end 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) -- 取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 s="{" local sep="" for e in pairs(set) do s=s..sep..e sep=", " end return s.."}" end function Set.print(s) print(Set.tostring(s)) end Set.mt.__add=Set.union Set.mt.__mul=Set.intersection s1=Set.new {10,20,30,40,50} s2=Set.new {30,40,50,60,70} print(getmetatable(s1)) -- table: 0x0004b540 print(getmetatable(s2)) -- table: 0x0004b540 s3=s1+s2 Set.print(s3) -- {60, 20, 10, 70, 50, 40, 30} Set.print((s1+s2)*s1) -- {50, 30, 20, 40, 10} -- 對於每個算術運算符,metatable 都有對應的域名與其對應,除了__add、__mul 外,還有__sub(減)、__div(除)、__unm(負)、__pow(冪), -- 咱們也能夠定義__concat 定義 鏈接行爲。__call能夠將table按照函數的方法調用,觸發__call對應的函數執行 Set.print(s1+8) --/usr/local/bin/luajit: metatable/metamethods1.lua:64: attempt to `add' a set with a non-set value --stack traceback: --[C]: in function 'error' --metatable/metamethods1.lua:19: in function '__add' --metatable/metamethods1.lua:64: in main chunk --[C]: at 0x0100001770
Lua 選擇 metamethod 的原則:若是第一個參數存在帶有__add 域的 metatable, Lua使用它做爲 metamethod,和第二個參數無關;不然第二個參數存在帶有__add 域的 metatable, Lua 使用它做爲 metamethod 不然報錯。
舉例:
Set={} Set.mt={} function Set.new(t) local set={} setmetatable(set,Set.mt) for _,l in ipairs(t) do set[l]=true end return set end function Set.union(a,b) if getmetatable(a)~=Set.mt or getmetatable(b)~=Set.mt then error("attempt to `add' a set with a non-set value",2) end 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) -- 取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 s="{" local sep="" for e in pairs(set) do s=s..sep..e sep=", " end return s.."}" end function Set.print(s) print(Set.tostring(s)) end Set.mt.__add=Set.union Set.mt.__mul=Set.intersection Set.mt.__tostring=Set.tostring -- 特殊方法,print會調用metatable的tostring方法返回的字符串 Set.mt.__metatable = "not your business" -- 特殊方法,加上這個方法後,就沒法訪問對象的metatable以及修改了 Set.mt.__le=function(a,b) for k in pairs(a) do if not b[k] then return false end end return true end Set.mt.__lt=function(a,b) return a<=b and not (b<=a) end Set.mt.__eq=function(a,b) return a<=b and b<=a end s1=Set.new {10,20,30,40,50,60,70} s2=Set.new {30,40,50,60,70 } print(s1) -- {50, 30, 60, 70, 20, 40, 10} print(s2) -- {50, 70, 40, 60, 30} print(s1<=s2) -- false print(s1<s2) -- false print(s1>=s2) -- true print(s1>s2) -- true print(s1==s1*s2) -- false print(getmetatable(s1)) -- not your business print(setmetatable(s1,{})) --/usr/local/bin/luajit: metatable/metamethods2.lua:82: cannot change a protected metatable --stack traceback: -- [C]: in function 'setmetatable' -- metatable/metamethods2.lua:82: in main chunk -- [C]: at 0x0100001770
舉例:
-- 經過__index設置默認值 -- 訪問一個表的不存在的域,觸發 lua 解釋器去查找__index metamethod:若是不存在, 返回結果爲 nil;若是存在則由__index metamethod 返回結果。 window={} window.prototype={x=0,y=0,width=100,height=100 } window.mt={} function window.new(o) setmetatable(o,window.mt) return o end --window.mt.__index=function(table,key) -- return window.prototype[key] --end window.mt.__index=window.prototype -- 這樣也能夠,就不用特地定義一個匿名函數了 w=window.new({x=10,y=20}) print(w.width) -- 100 # 先尋找w中,沒找到width屬性,去它的metatable裏面調用__index方法,若是找到則返回,若是沒找到返回nil print(rawget(w,width)) -- nil # 不會經過__index的方式去獲取w中的屬性,若是沒有,直接返回nil -- __newindex metamethod用來對錶更新,__index則用來對錶訪問。當你給表的一個 缺乏的域賦值, -- 解釋器就會查找__newindex metamethod:若是存在則調用這個函數而不進行賦值操做。調用 rawset(t,k,v)不調用任何 metamethod 對錶 t 的 k 域賦值爲 v。 ------------------------------------------------------------------------------------------------------------------------ -- 使用key爲{}隱藏默認值 local key={} local mt={__index=function(t) return t[key] end } -- 返回t[{}]的值,意思是若是沒有找到key,則調用__index,永遠返回t[{}]的值 function setDefault(t,d) t[key]=d -- t[{}]=d setmetatable(t,mt) -- 設置mt爲metatable end table={x=10,y=20 } print(table.x,table.z) -- 10 nil # 訪問z找不到值 setDefault(table,0) print(table.x,table.z) -- 10 0 # 訪問z以前已經設置了默認值 ------------------------------------------------------------------------------------------------------------------------ -- 監控table的實現 local index={} local mt={ __index=function(t,k) print("*access to element" .. tostring(k)) return t[index][k] end, __newindex=function(t,k,v) print("update of element " .. tostring(k) .. " to " .. tostring(v)) t[index][k]=v end } function track(t) local proxy={} -- 建立代理table proxy[index]=t -- 將原始table賦值到代理table的{}鍵上,外部不能訪問 setmetatable(proxy,mt) -- 將mt設置到proxy的metatable上,當從proxy讀取不到值的時候就必然會調用metatable的__index,就能夠實現監控 return proxy end table={x=10,y=20} table=track(table) print(table.x) -- *access to elementx -- 10 table.z=30 -- update of element z to 30 ------------------------------------------------------------------------------------------------------------------------ -- 只讀__index實現 function readOnly(t) local proxy={} local mt={ __index=t, __newindex=function() -- 控制修改表,保持Proxy裏面是空的,那麼修改表就必定會走__newindex方法 error("attempt to update a read-only table",2) end } setmetatable(proxy,mt) return proxy end days = readOnly{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"} print(days[2]) -- Monday days[2]="Noday" --/usr/local/bin/luajit: metatable/metamethods3.lua:91: attempt to update a read-only table --stack traceback: --[C]: in function 'error' --metatable/metamethods3.lua:80: in function '__newindex' --metatable/metamethods3.lua:91: in main chunk --[C]: at 0x0100001770
Lua 的全部協同函數存放於 coroutine table 中。 create 函數用於建立新的協同程序,其只有一個參數:一個函數,即協同程序將要運行的代碼。若一切順利,返回值爲 thread類型,表示建立成功。一般狀況下, create 的參數是匿名函數。
協程有三個狀態:掛起態(suspended)、運行態(running)、中止態(dead)。建立協同程序成功時,其爲掛起態,即此時協同程序並未運行。咱們可用 status 函數檢查協同的狀態。
舉例:
-- 協程建立,狀態,執行 co= coroutine.create(function() -- 建立協程,此時處於掛起狀態 print("hello world") end) print(co) -- thread: 0x0004afa0 print(coroutine.status(co)) -- suspended coroutine.resume(co) -- hello world -- 運行協程,獲得想要的輸出。resume 運行在保護模式下,所以,若是協同程序內部存在錯誤, Lua 並不會拋出錯誤,而是將錯誤返回給 resume 函數。 print(coroutine.status(co)) -- dead -- 協程結束,檢查狀態爲dead
舉例:
-- yield掛起協程執行 co = coroutine.create(function() for i = 1,10 do print("co",i) coroutine.yield() end end) coroutine.resume(co) -- co 1 print(coroutine.status(co)) -- suspended coroutine.resume(co) -- co 2 coroutine.resume(co) -- co 3 coroutine.resume(co) -- co 4 coroutine.resume(co) -- co 5 coroutine.resume(co) -- co 6 coroutine.resume(co) -- co 7 coroutine.resume(co) -- co 8 coroutine.resume(co) -- co 9 coroutine.resume(co) -- co 10 print(coroutine.resume(co)) -- true # 最後一個yield後面的代碼被執行,這時候完全執行完了該協程 print(coroutine.resume(co)) -- false cannot resume dead coroutine # 再去執行協程,返回false,提示報錯 協程已經到了dead狀態 -- yield傳遞參數 co=coroutine.create(function(a,b,c) coroutine.yield(a+b,a-c) end) print(coroutine.resume(co,1,2,3)) -- true 3 -2 # 返回yield傳回來的結果 print(coroutine.resume(co)) -- true print(coroutine.resume(co)) -- false cannot resume dead coroutine co=coroutine.create(function() print("co",coroutine.yield()) -- co 1 2 # 傳遞參數給yield a,b=coroutine.yield() print(a,b) -- 1 2 end) coroutine.resume(co) coroutine.resume(co,1,2) coroutine.resume(co,1,2) -- return結果返回 co=coroutine.create(function() return 4,5 end) print(coroutine.resume(co)) -- true 4 5 # return的結果被resume接收了
舉例:
-- 協同是一種非搶佔式的多線 程。管道的方式下,每個任務在獨立的進程中運行,而協同方式下,每一個任務運行在 獨立的協同代碼中。 -- 管道在讀(consumer)與寫(producer)之間提供了一個緩衝,所以 二者相關的的速度沒有什麼限制,在上下文管道中這是很是重要的, -- 由於在進程間的切 換代價是很高的。協同模式下,任務間的切換代價較小,與函數調用至關,所以讀寫可 以很好的協同處理。 function receive(prod) -- 執行協程,將拿到結果返回 local status,value=coroutine.resume(prod) return value end function send(x) -- yield 夯住,等待下次調用 coroutine.yield(x) end function producer() -- 建立一個協程,讀取輸入,並調用send,返回輸入的值,並夯住 return coroutine.create(function() while true do local x=io.read() send(x) end end) end function filter(prod) -- 建立一個協程,調用receive執行producer建立的協程,拿到結果後格式化,後調用send,返回格式化後的結果,並夯住 return coroutine.create(function() local line = 1 while true do local x= receive(prod) x=string.format("%5d %s",line,x) send(x) line=line+1 end end) end function consumer(prod) -- 循環調用receive執行filter建立的協程,並接受返回結果,打印出來 while true do local x=receive(prod) io.write(x,"\n") end end consumer(filter(producer())) -- 多層嵌套執行協程
舉例:
-- 初版,用遞歸迭代器實現輸出全部組合 function Permgen(a,n) if n==0 then printResult(a) else for i=1,n do -- 將列表前面全部的數值和最後一個數值替換,都能出現一種狀況,而後遞歸調用,將遍歷出全部組合的狀況 a[n],a[i]=a[i],a[n] Permgen(a,n-1) a[n],a[i]=a[i],a[n] end end end function printResult(a) for i,v in ipairs(a) do io.write(v," ") end io.write("\n") end Permgen({1,2,3},3) --2 3 1 --3 2 1 --3 1 2 --1 3 2 --2 1 3 --1 2 3 ------------------------------------------------------------------------------------------------------------------------- -- 使用協程替換迭代器實現 function Permgen(a,n) if n==0 then coroutine.yield(a) else for i=1,n do a[n],a[i]=a[i],a[n] Permgen(a,n-1) a[n],a[i]=a[i],a[n] end end end function printResult(a) for i,v in ipairs(a) do io.write(v," ") end io.write("\n") end --function perm(a) -- 在這種狀況下,被下面的方法替代了 -- local n=table.getn(a) -- local co=coroutine.create(function()Permgen(a,n) end) -- return function() -- local code,res=coroutine.resume(co) -- return res -- end --end -- 通常狀況下,coroutine.wrap 比 coroutine.create 使用起來簡單直觀,前者更確切的提 供了咱們所須要的:一個能夠 resume 協同的函數, -- 然而缺乏靈活性,沒有辦法知道 wrap 所建立的協同的狀態,也沒有辦法檢查錯誤的發生。 function perm (a) local n = table.getn(a) return coroutine.wrap(function () Permgen(a, n) end) end for p in perm{"a","b","c"} do printResult(p) end
Lua 不存在類的概念,每一個對象定義他本身的行爲並擁有本身的形狀(shape)。然而,依據基於原型(prototype)的語言好比 Self 和 NewtonScript,在 Lua中仿效類的概念並不難。 在這些語言中, 對象沒有類。 相反, 每一個對象都有一個 prototype(原型),當調用不屬於對象的某些操做時,會最早會到 prototype 中查找這些操做。在這類語言中實現類(class)的機制,咱們建立一個對象,做爲其它對象的原型便可(原型對象爲類,其它對象爲類的 instance)。類與 prototype 的工做機制相同,都是定義了特定對象的行爲。
舉例:
-- 定義方法的時候帶上一個額外的參數,來表示方法做用的對象。這個參數常常爲 self 或者 this -- self 參數的使用是不少面嚮對象語言的要點。大多數 OO 語言將這種機制隱藏起來,這樣程序員沒必要聲明這個參數(雖然仍然能夠在方法內使用這個參數)。 -- Lua 也提供了經過使用冒號操做符來隱藏這個參數的聲明。冒號的效果至關於在函數定義和函數調用的時候,增長一個額外的隱藏參數。 Account={ balance=0, withdraw=function(self,v) self.balance=self.balance-v end } function Account:deposit(v) self.balance=self.balance+v end Account.deposit(Account,200.00) Account:withdraw(100.00) print(Account.balance)
舉例:
-- 類的繼承 Account={balance=0 } function Account:new(o) o=o or {} setmetatable(o,self) self.__index=self return o end function Account:deposit(v) self.balance=self.balance+v end function Account:withdraw(v) if v>self.balance then error("insufficient funds") end self.balance=self.balance-v end SpecialAccount=Account:new() -- Account的子類 function SpecialAccount:withdraw(v) if v-self.balance>=self:getLimit() then error("insufficient funds") end self.balance=self.balance-v end function SpecialAccount:getLimit() return self.limit or 0 end s=SpecialAccount:new({limit=1000.00}) -- SpecialAccount的子類 function s:getLimit() -- 若是子類s中定義了getLimit,則不會調用SpecialAccount中的getLimit return self.limit*0.10 or 0 end --s:withdraw(200) -- insufficient funds --print(s.balance) -- insufficient funds -- SpecialAccount 從 Account 繼承了 new 方法,當 new 執行的時候, self 參數指向SpecialAccount。因此, s 的 metatable 是 SpecialAccount, __index 也是 SpecialAccount。這樣, s 繼承了 SpecialAccount,後者繼承了 Account。當咱們執行:s:deposit(100.00) Lua 在 s 中找不到 deposit 域,他會到 SpecialAccount 中查找,在 SpecialAccount 中找不到,會到 Account 中查找。使得 SpecialAccount 特殊之處在於,它能夠重定義從父類中繼承來的方法(繼承和重寫父類方法) ------------------------------------------------------------------------------------------------------------------------ -- 多繼承 local function search(k,plist) for i=1,table.getn(plist) do local v=plist[i][k] if v then return v end end end function creatClass(...) local c={} local args={...} -- 這裏注意,...雖然在lua中是表明不定參數,可是要獲取這些不定參數,須要使用{...}獲取,會生成一個tables,裏面放了全部的參數,以及一個key爲n,對應總共有多少個參數被接受。 setmetatable(c,{__index=function(t,k) -- return search(k,args) local v=search(k,args) -- 這樣改造,訪問到一個函數在t中沒有的,則從父類中找到該方法後,將該方法賦值給子類t中, -- 加快以後訪問該方法的速度,缺點是沒法在以後修改父類的方法,由於修改後不會影響到子類 t[k]=v return v end}) c.__index=c function c:new(o) o=o or {} setmetatable(o,c) return o end return c end Named = {} function Named:getname() return self.name end function Named:setname(n) self.name = n end NamedAccount=creatClass(Account,Named) account=NamedAccount:new({name="Paul"}) print(account:getname())