Lua程序设计(4th) 第三部分 语言特性

第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())
 
 
 
 
 
 
原文地址:https://www.cnblogs.com/yyqng/p/14459591.html