lua的元表

接触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, {__eq = 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, ...)那就没有办法了.

看了一下午的资料总结的,写文章确实耗时间,都9点多了,呵呵.


参考资料:

http://www.benmutou.com/archives/1779










阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页