Lua中关于Metatables和Metamethods的原理和用法,你能详细解释一下吗?

2026-04-01 18:461阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2327个文字,预计阅读时间需要10分钟。

Lua中关于Metatables和Metamethods的原理和用法,你能详细解释一下吗?

Metatables 允许我们修改 table 的行为,任何表都可以是其自身的 metatable,一组相关的表可以共享同一个 metatable(描述它们共有的行为)。一个表也可以拥有自身的 metatable(描述其独有的行为)。

Metatables允许我们改变table的行为,任何一个表都可以是其他一个表的metatable,一组相关的表可以共享一个metatable(描述他们共同的行为)。一个表也可以是自身的metatable(描述其私有行为)

Lua中关于Metatables和Metamethods的原理和用法,你能详细解释一下吗?

1.getmetatable(t):获取t的metatable,setmetatable(t,tmetatable):设置t的metatable为tmetatable

t = {}; print(getmetatable(t)); t1 = {}; setmetatable(t,t1); if getmetatable(t) == t1 then print("t's metatable is t1"); end

2.对于每一个算术运算符,metatable都有对应的域名与其对应,如__add(加),__mul(乘),__sub(减),__div(除),__unm(负),__pow(幂),我们也可以定义concat定义连接行为。对于所有参数,Lua选择metamethod的原则:如果第一个参数存在带有对应域名(如__add)的metatable,Lua使用它作为metatable,和第二参数无关。否则第二个参数存在带有对应域名(如__add)的metatable,Lua使用它作为metamethod,否则报错。
注意:Lua不关心这种混合类型(如:s = s + 8)

3.Metatables也允许我们使用metamethods:__eq(等于),__lt(小于),__le(小于等于)给关系运算符赋予特殊的含义。对剩下的三个关系运算符没有专门的metamethod,因为Lua将a ~= b转换为not(a==b);a>b转换为b

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) 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 --当Lua试图对两个集合相加时,将调用这个函数,以两个相加的表作为参数 Set.mt.__add = Set.union; --乘 Set.mt.__mul = Set.intersection; s1 = Set.new{10,20,30,50}; s2 = Set.new{30,1}; print(getmetatable(s1)); print(getmetatable(s2)); s3 = s1 + s2; Set.print(s3); Set.print((s1 + s2) * s1); --s = Set.new{1,2,3} --报错输出:bad argument #1 to 'pairs' --s = s + 8 --小于等于 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{2,4}; s2 = Set.new{4,10,2}; print(s1 <= s2); print(s1 < s2); print(s1 >= s1); print(s1 > s1); print(s1 == s2 * s1); --tostring Set.mt.__tostring = Set.tostring; s1 = Set.new{10,4,5}; print(s1); --保护metatable不被setmetatable修改 Set.mt.__metatable = "not your business"; s1 = Set.new{}; print(getmetatable(s1)); --报错输出:cannot change a protected metatable --setmetatable(s1,{});

6.__index:当我们访问一个表的不存在的域,返回结果为nil,这是正确的,但并不一致正确。实际上,这种访问触发lua解释器去查找__index metamethod:如果不存在,返回结果为nil;如果存在则由__index metamethod返回结果。
由于__index metamethod在继承中的使用非常常见,所以Lua提供了一个更简洁的使用方式。__index metamethod不需要非是一个函数,他也可以是一个表。但它是一个函数的时候,Lua将table和缺少的域作为参数调用这个函数;当他是一个表的时候,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 Window.mt.__index = function(table,key) return Window.prototype[key]; end --上述函数可以写为:Window.mt.__index = Window.prototype w = Window.new{x = 10,y = 20}; --先找w中的width,找不到,找w的metatable即Window.mt的__Index print(w.width);

7.__newindex metamethod用来对表进行更新,__index则用来对表访问。当你给表的一个缺少的域赋值,解释器就会查找__newindex metamethod:如果存在则调用这个函数而不进行赋值操作。像__index一样,如果metamethod是一个表,解释器对指定的那个表,而不是原始的表进行赋值操作。另外,有一个raw函数可以绕过metamethod:调用rawset(t,k,v)不调用任何metamethod对表t的k域赋值为v。__index和__newindex metamethods的混合使用提供了强大的结构:从只读表到面向对象编程的带有继承默认值的表。

function setDefault(t,d) local mt = {__index = function() return d end}; setmetatable(t,mt); end tab = {x = 10,y = 20}; print(tab.x,tab.z); setDefault(tab,0); print(tab.x,tab.z); 输出:10 nil 10 0

t = {}; local _t = t; t = {}; 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 to element "..tostring(k).." to "..tostring(v)) _t[k] = v; --update original table end, } setmetatable(t,mt); t[2] = 'hello'; print(t[2]) 输出:*update to element 2 to hello *access to element 2 hello

--不知道这个的应用场景 --create private index local index = {}; local mt = { __index = function(t,k) print("*access to element "..tostring(k)) return t[index][k]; end __newindex = function(tk,k,v) pritn("*update of element "..tostring(k).." to "..tostring(v)); t[index][k] = v; end } function track(t) local proxy = {}; proxy[index] = t; setmetatable(proxy,mt); return proxy; end

--只读表 function readOnly(t) local proxy = {}; local mt ={ __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]); days[2] = "Noday"; 输出结果:Sunday lua: testLua:16: attempt to update a read-only table stack traceback:...

8.__mode制定表的weak性。在这个域存在的时候,必须是个字符串:如果这个字符串包含小写字母 ‘k’ ,这个table中的keys就是weak的;如果这个字符串包含小写字母 ‘v’,这个table中的values就是weak的。
Lua自动进行内存的管理。程序只能创建对象(表,函数等),而没有执行删除对象的函数。通过使用垃圾收集技术,Lua会自动删除那些失效的对象。垃圾回收器只能在确认对象失效之后才会进行收集;它是不会知道你对垃圾的定义的。
例子1:堆栈:有一个数组和指向栈顶的索引构成。你知道这个数组中有效的只是在顶端的那一部分,但Lua不那么认为。如果你通过简单的出栈操作提取一个数组元素,那么数组对象的其他部分对Lua来说仍然是有效的。同样的,任何在全局变量中声明的对象,都不是Lua认为的垃圾,即使你的程序中根本没有用到他们。这两种情况下,你应当自己处理它(你的程序),为这种对象赋nil值,防止他们锁住其他的空闲对象。
例子2:当你想在你的程序中对活动的对象(比如文件)进行收集的时候。可以在收集器中插入每一个新的对象。然而,一旦对象被插入收集器,它就不会再被收集!即使没有其他的指针指向它,收集器也不会做什么的。Lua会认为这个引用是为了阻止对象被回收的。
Weak表是一种用来告诉Lua一个引用不应该防止对象被回收的机制。一个weak引用是指一个不被Lua认为是垃圾的对象的引用。如果一个对象所有的引用指向都是weak,对象将被收集,而那些weak引用将会被删除。Lua通过weak table来实现weak引用:一个weak tables是指所有引用都是weak的table。这意味着,如果一个对象只存在于weak tables中,Lua将会最终将它收集。
表有keys和values,这2者都可能包含任何类型的对象。在一般情况下,垃圾收集器并不会收集作为keys和values属性的对象。也就是说,keys和values都属于强引用类型,他们可以防止他们指向的对象被回收。在一个weak tables中,keys和values也可能是weak的。那意味着这里存在三种类型的weak tables:weak keys组成的tables,weak values组成的tables,以及纯weak tables类型,他们的keys 和values都是weak的。与table本身的类型无关,当一个keys或者value被收集时,整个的入口都将从这个table中消失。
注意:只有对象才可以从一个weak table中被收集。比如数字和布尔值类型的值,都是不会被收集的。例如:如果我们在table中插入了一个数值型的key,它将永远不会被收集器从table中移除。当然,如果对应于这个数值型key的vaule被收集,那么它的整个入口将会从weak table中被移除。
关于字符串的一些细微差别:从上面的实现来看,尽管字符串是可以被收集的,他们仍然跟其他可收集对象有所区别。其他对象,比如tables和函数,他们都是显示的被创建。比如,不管什么时候当Lua遇到{}时,它建立了一个新的table。任何时候这个function()…end建立了一个新的函数(实际上是一个闭包)。从程序员的角度看,字符串是值而不是对象。所以,就像数值或布尔值,一个字符串不会从weak tables中移除(除非它所关联的vaule被收集)

a = {}; b = {}; setmetatable(a,b); b.__mode = "k"; key = {}; a[key] = 1; --覆盖了第一个key的值。当垃圾收集器工作时,在其他地方没有指向第一个key的引用 --所以它被收集了,因此相对应的table中的入口也同时被移除了。但是第二个key仍然是占用 --活动的变量key,所以它不会被收集。 key = {}; a[key] = 2; collectgarbage(); for k,v in pairs(a) do print(v); end

--记忆函数 local results = {}; setmetatable(result,{__mode = "v"}; function mem_loadstring(s) if results[s] then return results[s]; else local res = loadstring(s); results[s] = res; return res; end end

–设置默认值 local defaults = {}; –如果默认值没有weak的keys,它就会将所有的带默认值的tables设定为永久存在 setmetatable(default,{__mode = “k”}); local mt = {__index = function(t)return defaults[t] end} function setDefault(t,d) defaults[t] = d; setmetatable(t,mt); end

本文共计2327个文字,预计阅读时间需要10分钟。

Lua中关于Metatables和Metamethods的原理和用法,你能详细解释一下吗?

Metatables 允许我们修改 table 的行为,任何表都可以是其自身的 metatable,一组相关的表可以共享同一个 metatable(描述它们共有的行为)。一个表也可以拥有自身的 metatable(描述其独有的行为)。

Metatables允许我们改变table的行为,任何一个表都可以是其他一个表的metatable,一组相关的表可以共享一个metatable(描述他们共同的行为)。一个表也可以是自身的metatable(描述其私有行为)

Lua中关于Metatables和Metamethods的原理和用法,你能详细解释一下吗?

1.getmetatable(t):获取t的metatable,setmetatable(t,tmetatable):设置t的metatable为tmetatable

t = {}; print(getmetatable(t)); t1 = {}; setmetatable(t,t1); if getmetatable(t) == t1 then print("t's metatable is t1"); end

2.对于每一个算术运算符,metatable都有对应的域名与其对应,如__add(加),__mul(乘),__sub(减),__div(除),__unm(负),__pow(幂),我们也可以定义concat定义连接行为。对于所有参数,Lua选择metamethod的原则:如果第一个参数存在带有对应域名(如__add)的metatable,Lua使用它作为metatable,和第二参数无关。否则第二个参数存在带有对应域名(如__add)的metatable,Lua使用它作为metamethod,否则报错。
注意:Lua不关心这种混合类型(如:s = s + 8)

3.Metatables也允许我们使用metamethods:__eq(等于),__lt(小于),__le(小于等于)给关系运算符赋予特殊的含义。对剩下的三个关系运算符没有专门的metamethod,因为Lua将a ~= b转换为not(a==b);a>b转换为b

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) 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 --当Lua试图对两个集合相加时,将调用这个函数,以两个相加的表作为参数 Set.mt.__add = Set.union; --乘 Set.mt.__mul = Set.intersection; s1 = Set.new{10,20,30,50}; s2 = Set.new{30,1}; print(getmetatable(s1)); print(getmetatable(s2)); s3 = s1 + s2; Set.print(s3); Set.print((s1 + s2) * s1); --s = Set.new{1,2,3} --报错输出:bad argument #1 to 'pairs' --s = s + 8 --小于等于 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{2,4}; s2 = Set.new{4,10,2}; print(s1 <= s2); print(s1 < s2); print(s1 >= s1); print(s1 > s1); print(s1 == s2 * s1); --tostring Set.mt.__tostring = Set.tostring; s1 = Set.new{10,4,5}; print(s1); --保护metatable不被setmetatable修改 Set.mt.__metatable = "not your business"; s1 = Set.new{}; print(getmetatable(s1)); --报错输出:cannot change a protected metatable --setmetatable(s1,{});

6.__index:当我们访问一个表的不存在的域,返回结果为nil,这是正确的,但并不一致正确。实际上,这种访问触发lua解释器去查找__index metamethod:如果不存在,返回结果为nil;如果存在则由__index metamethod返回结果。
由于__index metamethod在继承中的使用非常常见,所以Lua提供了一个更简洁的使用方式。__index metamethod不需要非是一个函数,他也可以是一个表。但它是一个函数的时候,Lua将table和缺少的域作为参数调用这个函数;当他是一个表的时候,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 Window.mt.__index = function(table,key) return Window.prototype[key]; end --上述函数可以写为:Window.mt.__index = Window.prototype w = Window.new{x = 10,y = 20}; --先找w中的width,找不到,找w的metatable即Window.mt的__Index print(w.width);

7.__newindex metamethod用来对表进行更新,__index则用来对表访问。当你给表的一个缺少的域赋值,解释器就会查找__newindex metamethod:如果存在则调用这个函数而不进行赋值操作。像__index一样,如果metamethod是一个表,解释器对指定的那个表,而不是原始的表进行赋值操作。另外,有一个raw函数可以绕过metamethod:调用rawset(t,k,v)不调用任何metamethod对表t的k域赋值为v。__index和__newindex metamethods的混合使用提供了强大的结构:从只读表到面向对象编程的带有继承默认值的表。

function setDefault(t,d) local mt = {__index = function() return d end}; setmetatable(t,mt); end tab = {x = 10,y = 20}; print(tab.x,tab.z); setDefault(tab,0); print(tab.x,tab.z); 输出:10 nil 10 0

t = {}; local _t = t; t = {}; 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 to element "..tostring(k).." to "..tostring(v)) _t[k] = v; --update original table end, } setmetatable(t,mt); t[2] = 'hello'; print(t[2]) 输出:*update to element 2 to hello *access to element 2 hello

--不知道这个的应用场景 --create private index local index = {}; local mt = { __index = function(t,k) print("*access to element "..tostring(k)) return t[index][k]; end __newindex = function(tk,k,v) pritn("*update of element "..tostring(k).." to "..tostring(v)); t[index][k] = v; end } function track(t) local proxy = {}; proxy[index] = t; setmetatable(proxy,mt); return proxy; end

--只读表 function readOnly(t) local proxy = {}; local mt ={ __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]); days[2] = "Noday"; 输出结果:Sunday lua: testLua:16: attempt to update a read-only table stack traceback:...

8.__mode制定表的weak性。在这个域存在的时候,必须是个字符串:如果这个字符串包含小写字母 ‘k’ ,这个table中的keys就是weak的;如果这个字符串包含小写字母 ‘v’,这个table中的values就是weak的。
Lua自动进行内存的管理。程序只能创建对象(表,函数等),而没有执行删除对象的函数。通过使用垃圾收集技术,Lua会自动删除那些失效的对象。垃圾回收器只能在确认对象失效之后才会进行收集;它是不会知道你对垃圾的定义的。
例子1:堆栈:有一个数组和指向栈顶的索引构成。你知道这个数组中有效的只是在顶端的那一部分,但Lua不那么认为。如果你通过简单的出栈操作提取一个数组元素,那么数组对象的其他部分对Lua来说仍然是有效的。同样的,任何在全局变量中声明的对象,都不是Lua认为的垃圾,即使你的程序中根本没有用到他们。这两种情况下,你应当自己处理它(你的程序),为这种对象赋nil值,防止他们锁住其他的空闲对象。
例子2:当你想在你的程序中对活动的对象(比如文件)进行收集的时候。可以在收集器中插入每一个新的对象。然而,一旦对象被插入收集器,它就不会再被收集!即使没有其他的指针指向它,收集器也不会做什么的。Lua会认为这个引用是为了阻止对象被回收的。
Weak表是一种用来告诉Lua一个引用不应该防止对象被回收的机制。一个weak引用是指一个不被Lua认为是垃圾的对象的引用。如果一个对象所有的引用指向都是weak,对象将被收集,而那些weak引用将会被删除。Lua通过weak table来实现weak引用:一个weak tables是指所有引用都是weak的table。这意味着,如果一个对象只存在于weak tables中,Lua将会最终将它收集。
表有keys和values,这2者都可能包含任何类型的对象。在一般情况下,垃圾收集器并不会收集作为keys和values属性的对象。也就是说,keys和values都属于强引用类型,他们可以防止他们指向的对象被回收。在一个weak tables中,keys和values也可能是weak的。那意味着这里存在三种类型的weak tables:weak keys组成的tables,weak values组成的tables,以及纯weak tables类型,他们的keys 和values都是weak的。与table本身的类型无关,当一个keys或者value被收集时,整个的入口都将从这个table中消失。
注意:只有对象才可以从一个weak table中被收集。比如数字和布尔值类型的值,都是不会被收集的。例如:如果我们在table中插入了一个数值型的key,它将永远不会被收集器从table中移除。当然,如果对应于这个数值型key的vaule被收集,那么它的整个入口将会从weak table中被移除。
关于字符串的一些细微差别:从上面的实现来看,尽管字符串是可以被收集的,他们仍然跟其他可收集对象有所区别。其他对象,比如tables和函数,他们都是显示的被创建。比如,不管什么时候当Lua遇到{}时,它建立了一个新的table。任何时候这个function()…end建立了一个新的函数(实际上是一个闭包)。从程序员的角度看,字符串是值而不是对象。所以,就像数值或布尔值,一个字符串不会从weak tables中移除(除非它所关联的vaule被收集)

a = {}; b = {}; setmetatable(a,b); b.__mode = "k"; key = {}; a[key] = 1; --覆盖了第一个key的值。当垃圾收集器工作时,在其他地方没有指向第一个key的引用 --所以它被收集了,因此相对应的table中的入口也同时被移除了。但是第二个key仍然是占用 --活动的变量key,所以它不会被收集。 key = {}; a[key] = 2; collectgarbage(); for k,v in pairs(a) do print(v); end

--记忆函数 local results = {}; setmetatable(result,{__mode = "v"}; function mem_loadstring(s) if results[s] then return results[s]; else local res = loadstring(s); results[s] = res; return res; end end

–设置默认值 local defaults = {}; –如果默认值没有weak的keys,它就会将所有的带默认值的tables设定为永久存在 setmetatable(default,{__mode = “k”}); local mt = {__index = function(t)return defaults[t] end} function setDefault(t,d) defaults[t] = d; setmetatable(t,mt); end