第18章 迭代器和泛型for
18.1 迭代器和闭包
首先编写一个简单的迭代器
-- 工厂 local values = function(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 正是为了这种迭代而设计的 。
t = {10, 20, 30} --内部保存了迭代函数,因此不需要变量iter for element in values(t) do print(element) end
另一个示例如下。一般尽管迭代器本身有点复杂,但使用却很简单。
function allwords () local line = io.read() -- current line local pos = 1 -- current position in the line return function () -- iterator function while line do -- repeat while there are lines local w, e = string.match(line, "(%w+)()", pos) if w then -- found a word? pos = e -- next position is after this word return w -- return the word else line = io.read() -- word not found; try next line pos = 1 -- restart from first position end end return nil -- no more lines: end of traversal end end for word in allwords() do print(word) end
18.3 无状态迭代器
可以在多个循环中使用同一个这种不保存任何状态的迭代器,从而避免创建新闭包的开销 。
local function iter (t, i) i = i + 1 print("iter i = "..i) local v = t[i] if v then return i, v end end local function ipairs (t) -- 返回:迭代函数、不可变状态表、控制变量的初始值 return iter, t, 0 end local tbl = {111, 222, nil, 555} for k, v in ipairs(tbl) do --不断调用 iter(t, i)直至返回值为nil print(k.." "..v) end
输出
i = 1 1 111 i = 2 2 222 i = 3
函数 pairs 与函数 ipairs 类似,但函数 pairs 的迭代函数是 Lua 语言中的一个基本函数 next:
local function pairs (t) return next, t, nil end local tbl = {111, 222, nil, 555} for k, v in pairs(tbl) do --for k, v in next, tbl do --不断调用 next(t, k) 获得返回值 k, v,直至v为nil print(k.." "..v) end
输出
1 111 2 222 4 555
按字母顺序输出函数名
local lines = { ["luaH_set"] = 10, ["luaH_get"] = 24, ["luaH_present"] = 48, } a = {} for n in pairs(lines) do a[#a + 1] = n end table.sort(a) for _, n in ipairs(a) do print(n) end
输出
luaH_get
luaH_present
luaH_set
使用迭代函数按字母顺序输出函数名,复杂性被隐藏到了迭代器中
local lines = { ["luaH_set"] = 10, ["luaH_get"] = 24, ["luaH_present"] = 48, } function pairsByKeys (t, f) local a = {} for n in pairs(t) do a[#a + 1] = nend table.sort(a, f) local i = 0 return function () i = i + 1 return a[i], t[a[i]] end end for name, line in pairsByKeys(lines) do print(name, line) end
输出
luaH_get 24 luaH_present 48 luaH_set 10
第20章 元表和元方法
元表中的元方法(一个函数)可以定义一个值在面对一个未知操作时的行为
local Set = {} local mt = {} -- create a new set with the values of a given list 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) if getmetatable(a) ~= mt or getmetatable(b) ~= 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) local res = Set.new{} for k in pairs(a) do res[k] = b[k] end return res end -- presents a set as a string function Set.tostring (set) local l = {} -- list to put all elements from the set for e in pairs(set) do l[#l + 1] = tostring(e) end return "{" .. table.concat(l, ", ") .. "}" end --设置关系运算符的元方法 mt.__le = function (a, b) -- subset for k in pairs(a) do if not b[k] then return false end end return true end mt.__lt = function (a, b) -- proper subset 真子集 return a <= b and not (b <= a) end mt.__eq = function (a, b) return a <= b and b <= a end --设置算术运算符元方法 mt.__add = Set.union --元表中加入元方法(metamethod)__add mt.__mul = Set.intersection --元表中加入元方法(metamethod)__mul --设置库定义相关的元方法 mt.__tostring = Set.tostring s1 = Set.new{10, 20, 30, 50} s2 = Set.new{30, 1} print(getmetatable(s1)) --两个表使用的是同一个元表mt print(getmetatable(s2)) print(s1) -- print 会调用 tostring, tostring 调用 Set.tostring print(s1 + s2) -->{1, 20, 30, 10, 50} print(s1 * s2) -->{30} --s5 = s1 + 5 --报错并显示堆栈信息 s1 = Set.new{2, 4} s2 = Set.new{4, 10, 2} print(s1 <= s2) --> true print(s1 < s2) --> true print(s1 >= s1) --> true print(s1 > s1) --> false print(s1 == s2 * s1) --> true mt.__metatable = "not you business" --设置之后,用户既不能看到也不能修改集合的元表 print(getmetatable(s1)) --getmetatable会返回__metatable字段 --setmetatable(s1, {}) -- error mt.__metatable字段已设置,s1不能再修改元表
表相关的元方法:__index 和 __newindex
-- create the prototype with default values prototype = {x = 0, y = 0, width = 100, height = 100} local mt = {} -- create a metatable -- declare the constructor function function new (o) setmetatable(o, mt) return o end mt.__index = function (_, key) return prototype[key] end mt.__newindex = function (_, key, v) return end w = new{x=10, y=20} print(w.x) --> 10 print(w.width) --> 100 --w.width 不存在,使用 __index 检索原型并返回结果 mt.__index = prototype print(w.width) --> 100 --w.width 不存在,使用 prototype.width 作为结果 w.newkey = 100 -- 使用 __newindex 对 w.newkey 赋值 print(w.newkey) --> nil
具有默认值的表
local key = {} --唯一的键, 避免命名冲突 local mt = {__index = function (t) return t[key] end} function setDefault (t, d) t[key] = d setmetatable(t, mt) end tab1 = {x=10, y=20} print(tab1.z) --> nil setDefault(tab1, 0) print(tab1.z) --> 0 -- 对表 tab1 中不存在字段的访问都将调用它的 __index 元方法返回 d tab2 = {x=10, y=20} setDefault(tab2, 1) -- 使用相同的元表 mt,但有各自的默认值 t.___ print(tab2.z) --> 1 print(tab1.z) --> 0
跟踪对表的访问
function track (t) local proxy = {} -- proxy table for 't' -- create metatable for the proxy local mt = { __index = function (_, k) print("*access to element " .. tostring(k)) return t[k] -- access the original table end, __newindex = function (_, k, v) print("*update of element " .. tostring(k) .. " to " .. tostring(v)) t[k] = v -- update original table end, __pairs = function () return function (_, k) -- iteration function local nextkey, nextvalue = next(t, k) if nextkey ~= nil then -- avoid last value print("*traversing element " .. tostring(nextkey)) end return nextkey, nextvalue end end, __len = function () return #t end } setmetatable(proxy, mt) return proxy end t = {[2] = "hi"} -- the original table print(t[2]) -- track 返回 proxy, t 变成 proxy 且一直为空表 -- 但是 track 会追踪原始的 t t = track(t) -- t 即 proxy 一直为空表,因此t[2] 会触发 __index用于跟踪表的访问 -- 但最终返回的值是原始表t中的值 print(t[2]) -- 赋值同上 t[2] = "hello" --> *update of element 2 to hello print(t[2]) --> *access to element 2 --> hello t = track({10, 20}) print(#t) --> 2 for k, v in pairs(t) do print(k, v) end --> *traversing element 1 --> 1 10 --> *traversing element 2 --> 2 20
只读的表
function readOnly (t) local proxy = {} local mt = { -- create metatable __index = t, __newindex = function (t, k, v) 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[1]) --> Sunday days[2] = "Noday" --> stdin:1: attempt to update a read-only table
第21章 面向对象编程
可以参考基于原型的语言 ( prototype-based language ) 中的 一些做法来在 Lua 语言中模拟类,只需要创建一个专 门被用作其他对象(类 的实例 )的原型对象即可 。如果有两个对象 A 和 B ,要让 B 成为 A 的一个原型,只需要
setmetatable(A, {__index = B})
在此之后, A 就会在 B 中查找所有它没有的操作。如果把 B 看作对象 A 的类, 则只不过是术语上的一个变化 。
类的使用示例:
--类 Account = { balance = 0, -- 默认值 withdraw = function (self, v) if v > self.balance then error "insufficient funds" end self.balance = self.balance - v end } function Account:deposit (v) self.balance = self.balance + v end function Account:new (o) o = o or {} -- create table if user does not provide one self.__index = self --隐藏的参数 self 得到的实参是 Account setmetatable(o, self) return o end --给 a.balance 赋了初始的金额。 a 有了自己的 balance 字段,后面对 a.balance 的访问就不会再涉及元方法了 a = Account:new{balance = 0} --实际上调用的是 a.deposit(a, 300.00),冒号不过是语法糖 --Lua 语言无法在表 a 中找到字段"deposit", 所以它会在元表的 __index 中搜索 --因此等价于调用 getmetatable(a).__index.deposit(a, 300.00) --也就是 Account.deposit(a, 300.00),a作为 self 参数。因此继承了 Account.deposit 及其他字段 a:deposit(300.00) a:withdraw(100.00) print(a.balance) --> 200 --继承 SpecialAccount = Account:new() --s 的元表是SpecialAccount s = SpecialAccount:new{limit = 1000.00} --Lua 语言无法在表 s 中找到字段 "deposit", 在元表的 __index(即 SpecialAccount) 中仍然找不到, --最终在 Account 中 找到 deposit 的实现 s:deposit(600.00) 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:withdraw(800.00) print(s.balance) --> 500 --一种多重继承的实现 -- look up for 'k' in list of tables 'plist' local function search (k, plist) for i = 1, #plist do local v = plist[i][k] -- try 'i'-th superclass if v then return v end end end function createClass (...) local c = {} -- new class local parents = {...} -- list of parents -- class searches for absent methods in its list of parents setmetatable(c, {__index = function (t, k) --(1) return search(k, parents) end}) setmetatable(c, {__index = function (t, k) --(2) local v = search(k, parents) t[k] = v -- save for next access return v end}) -- prepare 'c' to be the metatable of its instances c.__index = c -- define a new constructor for this new class 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 --NA 同时继承 Account 和 Named NA = createClass(Account, Named) --即 c acc = NA:new{name = "Paul"} --即 o -- acc:getname 不存在, 因此查找 acc.__index, 即 NA -- NA.getname 也不存在, 因此查找 NA.__index, 即 search(k), parents) -- 由于多继承的搜索具有一定的复杂性,因此使用(2)t[k]保存搜索结果以提升性能,再次访问就会像访问局部变量一样快了 print(acc:getname()) --> Paul
私有性
function newAccount (initialBalance) local self = {balance = initialBalance} local withdraw = function (v) self.balance = self.balance - v end local deposit = function (v) self.balance = self.balance + v end local getBalance = function() return self.balance end return { withdraw = withdraw, deposit = deposit, getBalance = getBalance } end acc1 = newAccount(100.00) -- 只能通过函数访问内部状态表 self acc1.withdraw(40.00) print(acc1.getBalance()) --> 60 function newAccount (initialBalance) local self = { balance = initialBalance, LIM = 10000.00, } local extra = function () if self.balance > self.LIM then return self.balance * 0.10 else return 0 end end local getBalance = function () return self.balance + extra() end return { getBalance = getBalance } end acc1 = newAccount(100000.00) -- 用户无法访问 extra 函数,extra 即为私有方法 print(acc1.getBalance()) --> 110000
一个在内部保存了状态的迭代器就是一个单方法对象。一种情况是,这个方法其实是一个根据不同的参数完成不同任务的分发方法。一种原型实现如下:
function newObject (value) return function (action, v) if action == "get" then return value elseif action == "set" then value = v else error("invalid action") end end end -- 每个对象使用一个闭包,要比使用一个表的开销更低 -- 虽然无法实现继承,但却可以拥有完全的私有性: d = newObject(0) print(d("get")) --> 0 d("set", 10) print(d("get")) --> 10
一个表,尤其是可以使用其他的表来索引一个表。例如,在银行账户的实现中,可以把所有账户的余额放在表 balance 中
-- 如果表 balance 是一个在模块 Account 内部保存的局部变量、 -- 那么只有模块内部的函数才能访问它 。 因此保证了它的安全性。 --这种实现的一大缺陷是: 一旦我们把账户作为表 balance 中的键,那么这个账户对于垃圾收集器而言就永远也不会变成垃圾 local balance = {} Account = {} function Account:withdraw (v) balance[self] = balance[self] - v end function Account:deposit (v) balance[self] = balance[self] + v end function Account:balance () return balance[self] end function Account:new (o) o = o or {} -- create table if user does not provide one setmetatable(o, self) self.__index = self balance[o] = 0 -- initial balance return o end --对偶表示无须修改即可实现继承 --We use this class just like any other one: a = Account:new{} a:deposit(100.00) print(a:balance())