programming in lua 初读18

初学者,有错误希望指正。

18————————————Metatables and Metamethods
--lua中不可以对两个表执行操作,也不能比较大小
--metatables 允许我们改变table的行为
--以相加为例,local a={} b={} a+b 
--1 检查其中一个表是否带有metatale
--2 检查metatable是否有_add域
--3 如果有则调用_add函数
--[[
   有趣的一点
   任何一个表都可以是其他表的metatable,一组相关的表可以共享一个metatable(描述他们的共同行为)
   一个表也可以是自身的metatable(描述其私自行为)
]]
--使用getmetatable()获取metatable,默认创建的表是一个不带metatable的表
t={}
print(getmetatble(t))  -->nil
--s使用setmetatable函数设置或者改变一个表的meteatable
t1={}
setmetatable(t,t1)
print(getmetatable(t)) --> t1
————————————————————————
#!/usr/local/bin/lua
Set={}
function  Set.new(t)
   local set={}
   setmetatable(set,Set.mt)
   --创建表的同时指定对应的metatable,这样使用set.new创建的所有集合都有相同的metatabla
   for _,l in ipairs(t) do set[l]=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 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
function list_print(s)
  for k,v in pairs(s) do
      print(k,v)
  end
end
--定义一个普通的表作为metatale,避免污染命名空间,将其放在set内部
Set.mt={}
Set.mt.__add=Set.union
s1=Set.new{10,20,30,50}
s2=Set.new{30,34}
--print(getmetatable(s1))
--print(getmetatable(s2))
--给metatable增加__add函数
Set.mt.__add=Set.union
--[[
list_print(s1)
list_print(s2)
s3=s1+s2
list_print(s1)
list_print(s2)
list_print(s3)
print(type(s3))
Set.print(s3)
print(type(Set.tostring(s3)))
]]
Set.mt.__mul=Set.intersection
--可以通过设置不同的操作进行复杂操作,基础算术运算__add(加号),__mul(乘号),__sub(减号),__div(除号),__unm(负号)
--__pow(幂),这些只是表示符,真正代表操作的其实是=(等号)后的函数,并不一定就是与标识符名称一样的功能
--__concat : 定义连接行为
--  __ 这是双下划线,漏了话会报错attempt to perform arithmetic on a table value
Set,mt.__tostring=Set.tostring
--[[
  库定义 的metamethods
  metatable是一个普通的表,任何人都可以使用,可以在自己的metatables中定义自己的域
  以tostring为例。print总是调用tostring来格式化输出
  但是tostring会检查对象是否存在一个带有__tostring域调用自己定义的函数
输出
]]
Set.mt.__metatable="not your business"
print(getmetatable(s1))
setmetatable(s1,{})
--[[
   setmetatable/getmetatable 函数也会使用metafield.在这种情况下,可以保护metatables,
   通过对metatable设置了__metatable的值,getmetatale会返回这个值,setmetatable会出错
   保护集合的同时也使使用者既不能看到也不能修改metatablee
   输出
not your business
-/usr/local/bin/lua: ./metatable_test.lua:64: cannot change a protected metatable
]]
Set.print((s1+s2)*s1)
--[[
算术运算和关系运算的metamethods
————————————————————————————————————————————
以下摘自
http://manual.luaer.cn/2.8.html
"add": + 操作。
下面这个 getbinhandler 函数定义了 Lua 怎样选择一个处理器来作二元操作。 首先,Lua 尝试第一个操作数。 如果这个东西的类型没有定义这个操作的处理器,然后 Lua 会尝试第二个操作数。


     function getbinhandler (op1, op2, event)
       return metatable(op1)[event] or metatable(op2)[event]
     end
通过这个函数, op1 + op2 的行为就是


     function add_event (op1, op2)
       local o1, o2 = tonumber(op1), tonumber(op2)
       if o1 and o2 then  -- 两个操作数都是数字?
         return o1 + o2   -- 这里的 '+' 是原生的 'add'
       else  -- 至少一个操作数不是数字时
         local h = getbinhandler(op1, op2, "__add")
         if h then
           -- 以两个操作数来调用处理器
           return h(op1, op2)
         else  -- 没有处理器:缺省行为
           error(···)
         end
       end
     end
"sub": - 操作。 其行为类似于 "add" 操作。
"mul": * 操作。 其行为类似于 "add" 操作。
"div": / 操作。 其行为类似于 "add" 操作。
"mod": % 操作。 其行为类似于 "add" 操作, 它的原生操作是这样的 o1 - floor(o1/o2)*o2
"pow": ^ (幂)操作。 其行为类似于 "add" 操作, 它的原生操作是调用 pow 函数(通过 C math 库)。
"unm": 一元 - 操作。
     function unm_event (op)
       local o = tonumber(op)
       if o then  -- 操作数是数字?
         return -o  -- 这里的 '-' 是一个原生的 'unm'
       else  -- 操作数不是数字。
         -- 尝试从操作数中得到处理器
         local h = metatable(op).__unm
         if h then
           -- 以操作数为参数调用处理器
           return h(op)
         else  -- 没有处理器:缺省行为
           error(···)
         end
       end
     end
"concat": .. (连接)操作,
     function concat_event (op1, op2)
       if (type(op1) == "string" or type(op1) == "number") and
          (type(op2) == "string" or type(op2) == "number") then
         return op1 .. op2  -- 原生字符串连接
       else
         local h = getbinhandler(op1, op2, "__concat")
         if h then
           return h(op1, op2)
         else
           error(···)
         end
       end
     end
"len": # 操作。
     function len_event (op)
       if type(op) == "string" then
         return strlen(op)         -- 原生的取字符串长度
       elseif type(op) == "table" then
         return #op                -- 原生的取 table 长度
       else
         local h = metatable(op).__len
         if h then
           -- 调用操作数的处理器
           return h(op)
         else  -- 没有处理器:缺省行为
           error(···)
         end
       end
     end
关于 table 的长度参见 §2.5.5 。


"eq": == 操作。 函数 getcomphandler 定义了 Lua 怎样选择一个处理器来作比较操作。 元方法仅仅在参于比较的两个对象类型相同且有对应操作相同的元方法时才起效。
     function getcomphandler (op1, op2, event)
       if type(op1) ~= type(op2) then return nil end
       local mm1 = metatable(op1)[event]
       local mm2 = metatable(op2)[event]
       if mm1 == mm2 then return mm1 else return nil end
     end
"eq" 事件按如下方式定义:


     function eq_event (op1, op2)
       if type(op1) ~= type(op2) then  -- 不同的类型?
         return false   -- 不同的对象
       end
       if op1 == op2 then   -- 原生的相等比较结果?
         return true   -- 对象相等
       end
       -- 尝试使用元方法
       local h = getcomphandler(op1, op2, "__eq")
       if h then
         return h(op1, op2)
       else
         return false
       end
     end
a ~= b 等价于 not (a == b) 。


"lt": < 操作。
     function lt_event (op1, op2)
       if type(op1) == "number" and type(op2) == "number" then
         return op1 < op2   -- 数字比较
       elseif type(op1) == "string" and type(op2) == "string" then
         return op1 < op2   -- 字符串按逐字符比较
       else
         local h = getcomphandler(op1, op2, "__lt")
         if h then
           return h(op1, op2)
         else
           error(···);
         end
       end
     end
a > b 等价于 b < a.


"le": <= 操作。
     function le_event (op1, op2)
       if type(op1) == "number" and type(op2) == "number" then
         return op1 <= op2   -- 数字比较
       elseif type(op1) == "string" and type(op2) == "string" then
         return op1 <= op2   -- 字符串按逐字符比较
       else
         local h = getcomphandler(op1, op2, "__le")
         if h then
           return h(op1, op2)
         else
           h = getcomphandler(op1, op2, "__lt")
           if h then
             return not h(op2, op1)
           else
             error(···);
           end
         end
       end
     end
a >= b 等价于 b <= a 。 注意,如果元方法 "le" 没有提供,Lua 就尝试 "lt" , 它假定 a <= b 等价于 not (b < a) 。


"index": 取下标操作用于访问 table[key] 。
     function gettable_event (table, key)
       local h
       if type(table) == "table" then
         local v = rawget(table, key)
         if v ~= nil then return v end
         h = metatable(table).__index
         if h == nil then return nil end
       else
         h = metatable(table).__index
         if h == nil then
           error(···);
         end
       end
       if type(h) == "function" then
         return h(table, key)      -- 调用处理器
       else return h[key]          -- 或是重复上述操作
       end
     end
"newindex": 赋值给指定下标 table[key] = value 。
     function settable_event (table, key, value)
       local h
       if type(table) == "table" then
         local v = rawget(table, key)
         if v ~= nil then rawset(table, key, value); return end
         h = metatable(table).__newindex
         if h == nil then rawset(table, key, value); return end
       else
         h = metatable(table).__newindex
         if h == nil then
           error(···);
         end
       end
       if type(h) == "function" then
         return h(table, key,value)    -- 调用处理器
       else h[key] = value             -- 或是重复上述操作
       end
     end
"call": 当 Lua 调用一个值时调用。
     function function_event (func, ...)
       if type(func) == "function" then
         return func(...)   -- 原生的调用
       else
         local h = metatable(func).__call
         if h then
           return h(func, ...)
         else
           error(···)
         end
       end
     end
     ____________________________________________________________________
]]
--表相关的metamethods
--针对表的不存在的域的查询和修改,提供的行为方法
--访问表一个不存在的域。返回nil.具体流程是访问触发解释器去查找__index metamethod :如果不存在,返回nil,如果存在,由__index metamethod返回结果
#!/usr/local/bin/lua
Window={}
Window.prototype={x=0,y=0,width=100,height=100,}
Window.mt={}
function Window.new(o)
  setmetatable(o,Window.mt)
  return o
end
--定义__index metamethod
Window.mt.__index=function(table,key)
   return Window.prototype[key]
end
w=Window.new{x=10,y=20}
print(w.width)
--[[
访问w缺少的width,lua发现w不存在width,但是有一个metatale带有__index域,lua使用w(table)和width(缺少的值)来调用__index metamethod,metamethod 则通过访问原型表(prototype)获取缺少的域的结果
接近Lua 具名实参(通过table实现可选参数函数)效果
检查参数和设置默认参数值的函数
function func1(a)
    print("this is fakeone")
    --[[
    if func_os~=nil and func_3~=nil then
       func_os(func_3)
    end
    ]]
    _func1(a.x,a.func_os or nil,a.func_3 or nil)
 
end
真正实现功能的函数
function _func1(x,func_os,func_3)
   print("this is correct one")
   if func_os~=nil and func_3~=nil then
       func_os(func_3)
   end
   print(type(func_os),type(func_3))
end
]]
--__index metamethod在继承中的使用非常常见
--不需要非要是一个函数,可以是一个表,当它是一个函数的时候,
--lua将table和缺少的域作为参数调用这个函数,
--当是一个表示,lua将在这个表中看是否有缺少的域
Window.mt.__index=Window.prototype
--这是查找__index域时,访问prototype表获取缺少的值
--相当于执行Window.prototype["width"]
--推荐使用函数,因为这将更加的灵活
--当不想调用__index metamethod来访问一个表,使用rawget(t,i)的调用以raw access方式访问表。
--__index访问表,用来对表访问访问默认的table有但是赋值的table缺少的域获得默认值,不存在返回nil
--__newindex更新表,给表一个缺少的域赋值,解释器会查找__newindex metamethod,如果存在则调用函数而不进行赋值操作,rawset(t,k,v)不调用任何metamethod 对表t的k域赋值为v
--__index和__newindex的混合使用提供了强大的结构:从只读表到面向对象编程的带有继承默认值得表
--都是只有当表中访问的域不存在时候才起作用
--[[
  摘自http://www.runoob.com/lua/lua-metatables.html
  __index 元方法
这是 metatable 最常用的键。
当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index 键。如果__index包含一个表格,Lua会在表格中查找相应的键。
我们可以在使用 lua 命令进入交互模式查看:
$ lua
Lua 5.3.0  Copyright (C) 1994-2015 Lua.org, PUC-Rio
> other = { foo = 3 } 
> t = setmetatable({}, { __index = other }) 
> t.foo
3
> t.bar
nil
如果__index包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。
__index 元方法查看表中元素是否存在,如果不存在,返回结果为 nil;如果存在则由 __index 返回结果。
mytable = setmetatable({key1 = "value1"}, {
  __index = function(mytable, key)
    if key == "key2" then
      return "metatablevalue"
    else
      return nil
    end
  end
})


print(mytable.key1,mytable.key2)
实例输出结果为:
value1    metatablevalue
实例解析:
mytable 表赋值为 {key1 = "value1"}。
mytable 设置了元表,元方法为 __index。
在mytable表中查找 key1,如果找到,返回该元素,找不到则继续。
在mytable表中查找 key2,如果找到,返回 metatablevalue,找不到则继续。
判断元表有没有__index方法,如果__index方法是一个函数,则调用该函数。
元方法中查看是否传入 "key2" 键的参数(mytable.key2已设置),如果传入 "key2" 参数返回 "metatablevalue",否则返回 mytable 对应的键值。
我们可以将以上代码简单写成:
mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
print(mytable.key1,mytable.key2)
总结
Lua查找一个表元素时的规则,其实就是如下3个步骤:
1.在表中查找,如果找到,返回该元素,找不到则继续
2.判断该表是否有元表,如果没有元表,返回nil,有元表则继续。
3.判断元表有没有__index方法,如果__index方法为nil,则返回nil;如果__index方法是一个表,则重复1、2、3;如果__index方法是一个函数,则返回该函数的返回值。
__newindex 元方法
__newindex 元方法用来对表更新,__index则用来对表访问 。
当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法:如果存在则调用这个函数而不进行赋值操作。
以下实例演示了 __newindex 元方法的应用:
mymetatable = {}
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })


print(mytable.key1)


mytable.newkey = "新值2"
print(mytable.newkey,mymetatable.newkey)


mytable.key1 = "新值1"
print(mytable.key1,mymetatable.key1)
以上实例执行输出结果为:
value1
nil    新值2
新值1    nil
以上实例中表设置了元方法 __newindex,在对新索引键(newkey)赋值时(mytable.newkey = "新值2"),会调用元方法,而不进行赋值。而如果对已存在的索引键(key1),则会进行赋值,而不调用元方法 __newindex。
以下实例使用了 rawset 函数来更新表:
mytable = setmetatable({key1 = "value1"}, {
  __newindex = function(mytable, key, value)
        rawset(mytable, key, "\""..value.."\"")


  end
})


mytable.key1 = "new value"
mytable.key2 = 4


print(mytable.key1,mytable.key2)
以上实例执行输出结果为:
new value    "4"
]]
——————————————————————————————————————————————————
--监控表
--监控一个表的所有访问情况,建立代理,代理是一个空表,并且带有__index和__newindex metamethods,由这两个方法扶着跟踪表的所有访问情况,并将其指向原始的表
--捕获对一个表的所有访问情况唯一的方法就是保持表为空
————————————-
--小插曲
#!/usr/local/bin/lua
jlmin=function(kjl,rjl,tjl)
   local  min=kjl
   if min>rjl then
     min=rjl
   --[[
   elseif min>tjl then
   --逻辑出错,如果前面是对的就不会执行下一个
   ]]
   end
   if min>tjl then
     min=tjl
   end
   return min
end
kjl=4
rjl=2
tjl=3
Min=jlmin(4,2,3)
JL=function(Min)
  local jl=nil
  local min=Min
  print(Min)
  if min==kjl then
      jl=0
  elseif min==rjl then
       jl=1
  elseif min==tjl then
      jl=2
  end
  return jl
end
print(JL(Min))
--犯了逻辑错误,最近习惯了代码执行后调错误,代码僵化,今后要注意代码编程阶段不要出错
--谨记谨记
#!/usr/local/bin/lua
--[[
   t = {}     -- 原始表
 
--对原始表的私有访问
local _t = t
 
-- create proxy
--创建代理
t = {}
 
-- create metatable
local mt = {
    __index = function (t,k)
    print("*access to element " .. tostring(k))
    return _t[k]  -- access the original table
    end,
 
    __newindex = function (t,k,v)
    print("*update of element " .. tostring(k) ..
               " to " .. tostring(v))
    _t[k] = v      -- update original table
    end
}
setmetatable(t, mt)
]]
--监控多张表
--创建一个私有的key,保证metatable的不被用作其它用途,
-- create private index
local index = {}
-- create metatable
local mt = {
    __index = function (t,k)
       print("*access to element " .. tostring(k))
       return t[index][k]   -- access the original table
    end
 
    __newindex = function (t,k,v)
    print("*update of element " .. tostring(k) .. " to "
                  .. tostring(v))
    t[index][k] = v          -- update original table
    end
}
 
function track (t)
    local proxy = {}
    proxy[index] = t
    setmetatable(proxy, mt)
    return proxy
end
--想要监控表时,使用t=track(t)
——————————————————————————————————————————————————————
--只读表
--利用代理的思想实现
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
enddays = readOnly{"Sunday", "Monday", "Tuesday", "Wednesday",
       "Thursday", "Friday", "Saturday"}
 
print(days[1])       --> Sunday
days[2] = "Noday"
stdin:1: attempt to update a read-only table


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部