接触lua一年多,是要把lua的高级特性总结一下了.先说说元表吧.
元表是什么?为什么要有元表?
学过C++的童鞋应该知道操作符重载.例如,自定义一个复数类,可以让他的两个实例相加减等等.
同理,lua的元表也可以让两个表相加减,但是他使用了更简单的语法.lua的元表是为了扩展lua的特性,使他的功能更加丰富.
要想使一个表拥有加法操作的功能,必须给他添加一种元表__add,语法如下:
setmetatable(t, {__add = function(t1, t2) return t1.a + t2.b end})
当调用tab1 + tab2时,解释器不再报错,而是会调用上面的__add函数.不要问我为什么可以这样,要想知道答案,去看lua源代码好咯.
把上面的tab1,tab2任意一个设置__add元表都可以. tab1+tab2时会调用__add. tab1传给参数t1, tab2传给参数t2.
除了加法,还可以重定义哪些隐藏的操作.
一般的运算和逻辑操作都可以重定义.有二元的,也有一元的.
下面的表包括了常见的运算符.
模式 | 描述 |
---|---|
__add | 对应的运算符 '+'. |
__sub | 对应的运算符 '-'. |
__mul | 对应的运算符 '*'. |
__div | 对应的运算符 '/'. |
__mod | 对应的运算符 '%'. |
__unm | 对应的运算符 '-'. |
__concat | 对应的运算符 '..'. |
__eq | 对应的运算符 '=='. |
__lt | 对应的运算符 '<'. |
__le | 对应的运算符 '<='. |
对于二元的__eq,再举个例子:
setmetatable(t, {__eq = function(a, b) return #a == #b end})
一元的__unm:
setmetatable(t, {__unm = function(a) local r = {} r[1] = -a[1] return r end})
t = {1}
调用ft = -t,则ft[1] = -1
忘了说了setmetatable返回要操作的表本身,所有我们一般这么调用:
local t = setmetatable({}, ...),临时表返回给一个变量.
注意,每种操作的函数返回值会不一样,比较操作只能返回bool,求长度只能返回integer,取反操作只能返回一个新的表.
除了扩展表的操作,还有什么秘密武器呢.就是下面要说的重点了,所有lua的高级特性,诸如模拟面向对象的继承,保护表元素等都离不开他.
先说说__index,如果这样设置:
t = {name = 'shonm'}
t_extra = {extra = 123}
setmetatable(t, {__index = t_extra })
t.name --他的值为 'shonm'
t.extra --在t表里我们没有定义extra字段,但是现在却可以访问到了,值为123,就是因为设置了元表__index
在查找t的extra字段时,没有找到,那么就会去他的元表的__index字段里查找,看是否有字段为extra.
如果就是很犟,非要知道,初始的表t里面有没有extra这个字段怎么办?还是有办法滴.调用rawget(t, 'extra')就好.
不要小看了这个过程,他的威力很强大,可以实现面向对象的特性了.例如下面的一段代码,就是子类访问父类的字段:
father = {
house = 1
}
son = {
car = 1
}
father.__index = father
setmetatable(son, father)
print(son.house) --1
还有构造新对象:
local Obj = {}
function Obj:new (obj)
obj = obj or {}
setmetatable(obj, self)
self.__index = self
return obj
end
local o = Obj:new({}) --o相当于Obj的一个实例,可以调用Obj的所有方法了
是不是很奇特!
元表__index相当于对数据访问的扩展,现在问题来了,那你能不能做数据保护呢?即定义好表的数据之后,不能往里面添加字段。元表__newindex就可以做到了。
简单的来说,__newindex就是当你要写表的数据,即向表添加新字段时,他会来接管。如果__newindex是函数,为了保护表可以在函数中抛出错误。例如:
local days = {"星期一", "星期二", "星期三"}
setmetatable(days, {__newindex = function()
error("do not modify me!!!");
end })
print(days[3]) --可以访问
days[4] = 'Tues' --do not modify me!!!
如果__newindex是一个表,那么这个值会赋给那个表。例如:
local ta = {}
local t = {}
setmetatable(t, {__newindex = ta })
t.money = 123
print(t.money, ta.money)
结果为nil, 123
给t.money赋值时会触发元表__newindex.
刚才数据保护的那段代码不通用,能否用一个函数实现数据保护.
__index和__newindex,结合就可以:
local function readOnly(t)
local newT = {};
local mt = {
__index = t,
__newindex = function()
error("do not modify me");
end
}
setmetatable(newT, mt);
return newT;
end
local t = {"星期一", "星期二", "星期三"}
local days = readOnly(t);
days[4] = 'Tues' --do not modify me!!!
上面的代码还有个漏洞就是,如果调用rawset(days, ...)那就没有办法了。
注意,__newindex只对表原本不存在的字段赋值时才会触发,即使是__index存在也会触发,例如:
function new_const( const_table )
local t = {d=789}
setmetatable(t, {__index = const_table, __newindex = function() error('update!!!') end})
return t
end
t = {a=1,b=2}
tt = new_const(t)
tt.c = 123 --触发__newindex
tt.a = 123 --也会触发__newindex,即使__index里有
tt.d = 456 --不会触发
看了一下午的资料总结的,写文章确实耗时间,都9点多了,呵呵。
后面还有一篇关于元表的补充:
参考资料:
http://www.benmutou.com/archives/1779