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