标签:
lua中每个值都有一套预定义的操作集合,比如数字是可以相加的,字符串是可以连接的,但是对于两个table类型,则不能直接进行“+”操作。这需要我们进行一些操作。在lua中有一个元表(metatable),我们可以通过元表来改变一个值的行为,使其在面对一个非预定义的操作时执行一个指定的操作。比如,现在有两个table类型的变量a和b,我们可以通过metatable定义如何计算表达式a+b,具体的在Lua中是按照以下步骤进行的:
1.先判断a和b两者之一是否有元表
2.检查该元表中是否有一个叫__add的字段
3.如果找到该字段,就调用该字段对应的值,这个值对应的是一个metamethod(元方法)
4.调用__add对应的元方法计算a和b的值
在table中,我们可以重新定义的元方法有以下几个:
__add(a, b) --加法
__sub(a, b) --减法
__mul(a, b) --乘法
__div(a, b) --除法
__mod(a, b) --取模
__pow(a, b) --乘幂
__unm(a) --相反数
__concat(a, b) --连接
__len(a) --长度
__eq(a, b) --相等
__lt(a, b) --小于
__le(a, b) --小于等于
__index(a, b) --索引查询
__newindex(a, b, c) --索引更新(PS:不懂的话,后面会有讲)
__call(a, ...) --执行方法调用
__tostring(a) --字符串输出
__metatable --保护元表
下面来进行一些个别的说明:
1.__add
local mytable = setmetatable({1,2,3},{ __add = function(mytable,newtable) for i=1,#newtable do table.insert(mytable,#mytable+1,newtable[i]) end return mytable end }) local secondtable = {4,5,6} mytable = mytable + secondtable for k,v in ipairs(mytable) do print(k,v) end
输出为:
1 1 2 2 3 3 4 4 5 5 6 6
注:由于mytable和secondtable都是table,所以它们不能简单的相加。这个时候,lua会去找mytable和secondtable,去看它们有没有__add方法,这时候,lua发现mytable中有__add,因此调用了__add对应的函数,__add对应的函数传入的第一个参数是mytable,第二个参数是secondtable。实际上就是相当于把secondtable的值都附加到mytable中。
现在大家有没有想过,如果两个table同时都有__add方法,那lua会执行哪个呢?我们看一下下面的代码:
local mytable = setmetatable({1,2,3},{ __add = function(mytable,newtable) print("这是第一个table") for i=1,#newtable do table.insert(mytable,#mytable+1,newtable[i]) end return mytable end }) local secondtable = setmetatable({4,5,6},{ __add = function(mytable,newtable) print("这是第二个table") for i=1,#newtable do table.insert(mytable,#mytable+1,newtable[i]) end return mytable end }) mytable = secondtable + mytable + secondtable + mytable for k,v in ipairs(mytable) do print(k,v) end
输出为:
这是第二个table 这是第二个table 这是第二个table 1 4 2 5 3 6 4 1 5 2 6 3 7 4 8 5 9 6 10 1 11 2 12 3 13 1 14 2 15 3
注:由输出我们可以知道,哪个table在“+”操作最前面,则执行该table的__add方法,而且该语句后面也是都是调用第一个table的__add方法。还有朋友可能不太理解为什么会输出1-15,不应该是1-12吗?其实是这样的我们在secondtable的__add方法已经把secondtable的值就行改变了,当他执行完secondtable + mytable这个的时候,它已经变成了4,5,6,1,2,3而mytable是在secondtable + mytable + secondtable+ mytable 执行完了之后才被赋值的,所以在右边的两个mytable都是1,2,3
接着我们在看一下__index元方法,当访问一个table中不存在的字段时,得到的结果为nil。这是对的,但并非完全正确。实际上,这些访问会促使解释器去查找一个叫__index的元方法。如果没有这个元方法,那么访问结果如前述的胃nil。否则,就由这个元方法来提供最终结果。看下面的例子:
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) print("-------------------",key) for k,v in pairs(table) do print(k,v) end return window.prototype[key] end w = window.new{x=10,y=20} print(w.width,w.x,w.y,w.height)
输出:
------------------- width y 20 x 10 ------------------- height y 20 x 10 100 10 20 100
注:lua检测到w中没有某字段,但在其元表中却有一个__index字段,那么lua就会以w(table)和"width"(不存在的key)来钓鱼这个__index元方法。随后元方法用这个key来索引原型table,并返回结果。__index元方法不一定是一个函数,它还可以是一个table,当它是一个函数时,lua以table和不存在的key作为参数来调用该函数,而当它是一个table时,lua就以相同的方式来重新访问这个table(会在这个table中继续查找)。
我们现在来看一下__newindex元方法:
__newindex元方法于__index类似,__newindex用于table的更新,而__index用于table的查询。当对一个table不存在的索引赋值时,解释器就会查找__newindex元方法。如果有这个元方法,解释器就调用它,而不是执行赋值。如果这个元方法是一个table,解释器就在该table中执行赋值,而不是对原来的table。看下面的例子:
local man = { name = "大神", money = 30000, play = function() print("我去打篮球") end } local t = {} local mt = { __index = man, __newindex = function(table,key,value) print(key .. "不存在,不要尝试给它赋值") end } setmetatable(t,mt) t.play = function() print("我去踢足球") end t.play()
输出:
play不存在,不要尝试给它赋值 我去打篮球
注:由输出我们可以知道t.play = function() print("hi") end并没有起到作用,这是因为给t的sayhello字段赋值的时候,lua判断play字段不存在,所以会去调用元表里的__newindex元方法。__newindex元方法被调用的时候会传入3个参数:table本身,字段名,想要赋予的值。
我们再看下面的例子:
local man = { name = "程序猿" } local updatetable = { name = "我是美男子" } local t = {} local mt = { __index = man, __newindex = updatetable } setmetatable(t,mt) print("updatetable赋值前:",updatetable.name) t.name = "我是大帅哥" print("updatetable赋值后:",updatetable.name) print("t.name:",t.name)
输出:
updatetable赋值前: 我是美男子 updatetable赋值后: 我是大帅哥 t.name: 程序猿
这就是上面所说的:如果这个元方法是一个table,解释器就在该table中执行赋值,而不是对原来的table。看下面的例子。
如果还不明白,在看下面的例子可能就会更加理解了:
mymetatable = {} mytable = setmetatable({key = "value"},{__newindex=mymetatable}) print(mytable.key) mytable.newkey = "newkey value" print(mytable.newkey,mymetatable.newkey) mytable.key = "key value" print(mytable.key,mymetatable.key)
输出:
value nil newkey value key value nil
注:当lua执行mytable.newkey = "newkey value"这句时,会去mytable中查找newkey,发现没有newkey的时候就会去找__newindex这个元方法,然后对__newindex对应的table进行赋值,因此mytable.newkey为nil,mymetatable.newkey为newkey value。而执行mytable.key = "key value"语句的时候,mytable存在key,因此只修改mytable中的key对应的值。
我们再来看一下__call这个元方法,这个元方法会在Lua 调用一个值时调用,看下面的例子:
mytable = setmetatable({10},{ __call = function(mytable,newtable) sum = 0 for i=1,#mytable do sum = sum + mytable[i] end for i=1,#newtable do sum = sum + newtable[i] end return sum end }) newtable = {10,20,30} print(mytable(newtable))
输出:
70
注:mytable是一个table,当调用其时(mytable(newtable))会执行__call这个元方法,第一个参数为mytable,第二个参数为newtable。
__tostring元方法,它主要是更改打印语句,看下面的例子:
mytable = setmetatable({},{ __tostring = function(mytable) return "我改变你的值" end }) print(mytable)
输出:
我改变你的值
标签:
原文地址:http://my.oschina.net/u/815187/blog/485908