1.元表
首先我们从形式上去认识lua的元表,lua通过两个函数去设置和获取一个值的元表。
t = {} mt = {} setmetatable(t,mt) --将mt设置为t的元表 getmetatable(t) --获取t的元表,结果为mt
元表从语法形式上就只有以上的内容了。那么元表用途是什么?
以我的理解,元表的作用是通过元方法去延展、补充原本表的功能。
ps:后续实例中,默认t是表,mt是t的元表。
2.算数运算元方法
lua中两个表之间如何去定义算数运算,是通过元表外加元方法的方式去实现的。下面举个简单的例子。
mt = {__add = function(t1,t2) for _,v in ipairs(t2) do table.insert(t1,v) end return t1 end} t1 = {1,2} setmetatable(t1,mt) t2 = {3} t = t1 + t2 -->{1,2,3}
我们用“__add”这个元方法实现了自定义表结构的算数运算。除了“__add”,还有一系列的算数运算方法:减法(__sub);除法(__div);负数(__unm);取模(__mod)。类似的还有一系列位运算相关的元方法这里就不一一列出来了。
3.特殊的元方法
__tostring,这个元方法正如他的样子,有这个值时,函数tostring就会调用这个元方法完成工作。(ps:print函数会默认调用输出值的tostring方法)。
__metatable:该元方法使得,getmetatable函数返回值为该值。
local t = {} setmetatable(t,{__metatable = "this is nil"}) getmetatalbe(t) --> "this is nil"
这时再去设置t的元表会报错。该元方法用于保护元表的访问。
__pairs:这个元方法用于实现pairs(t)时调用的函数。
4.表相关元方法
__index:当访问表中值为nil的键值时,则会调用这个值。这时有两种情况,如果__index的值为函数,则会返回该函数的运行结果。
mt.__index = function(t,k) --t 是表,k 是键值 end
如果__index是一个表的话,那么就会在这个表中去找键值为k对于的值并返回。
使用rawget(t,k),可以对表t进行原始访问,不会对mt进行访问。
__newindex:设置表中不存的键值时,会调用这个值。如同__index,存在两种情况。如果__newindex的值为函数,则返回该函数结果。
mt.__newindex = function(t,k,v) --t 是表,k 是键,v 是值 end
如果__newindex是一个表的话,那么设置一个t中不存在的键值时,会去元表中进行赋值操作。
使用rawset(t,k,v),可以绕开元表,对原始的表进行赋值操作。
5.__index和__newindex
在知道了__index和__newindex两个元方法的情况下,我们就可以很轻松的对于表的赋值和取值操作进行额外附加的操作了,例如,实现一个只读表,只需要简单的把__newindex设置为提示函数即可。
mt.__newindex = function(t,k,v) error(string.format("can not set %s %s value to %s",t.tostirng,k,v)) end
还有例如追踪表的访问等需求,都可以使用__index和__newindex配置使用实现,这里就不具体写出来了。
6.总结
由于lua中,所有变量的地位都是一样的。其表现就是,无论一个变量是数值还是字符串还是表,在语法上基本是等价。那么按道理,所有的变量都是可以设置元表的,但是lua层的setmetatable只能设置表的元表,其他变量类型包括函数设置元表,需要通过c代码去实现。
getmetatable()则没有限制可以获取所有类型值得元表,所有string类型的值,会有一个共同的元表,可以获取它然后然后加一些个人需要的操作,当然这会作用到全局所有string类型变量,所以不建议这么去做。
最后说明,其实除了以上提到的元方法,还有一些元方法这里没有说明,后续涉及到其他相关内容时,会加以补充。(例如:__call、__mode)