(转)lua元表

本文简译自一篇老外的博客,写得不错可惜我翻译的太烂,简译如下。

(key--value常见翻译为“键值对”,我翻译为索引、值)

在这篇教程里我会介绍Lua中一个重要的概念:metatable(元表),掌握元表可以让你更有效的

使用Lua。每一个tabel都可以附加元表,元表是带有索引集合的表,它可以改变被附加表的行为。

看下例:

t={}--普通表

mt={}--元表,现在暂时什么也没有

setmetatable(t,mt)--把mt设为t的元表

getmetatable(t)--这回返回mt

如你所见getmetatable和setmetatable是主要的函数。当然我们可以把上面的三行代码合为:

t=setmetatable({},{})

setmetatable返回第一个参数,因此我们可以使用这个简短的表达式。现在,我们在元表里放些什

么呢?元表可以包含任何东西,但是元表通常以"__"(两个下划线)开头的索引(当然string类型)

来调用,例如__index和__newindex。和索引对应的值可以是表或者函数,例如:

t=setmetatable({},{

__index=function(t,key)

ifkey=="foo"then

return0

else

returntable[key]

end

end

})

我们给__index索引分配了一个函数,让我们来看看这个索引是干啥的。

__index

元表里最常用的索引可能是__index,它可以包含表或函数。

当你通过索引来访问表,不管它是什么(例如t[4],t.foo,和t["foo"]),以及并没有分配索引的值时,

Lua会先在查找已有的索引,接着查找表的metatable里(如果它有)查找__index索引。如果

__index包含了表,Lua会在__index包含的表里查找索引。这听起来很迷糊,让我们看一个例子。

other={foo=3}

t=setmetatable({},{__index=other})

t.foo--3,现在__index包含的表{foo=3}查找

t.bar--nil,没找到

如果__index包含一个函数,当被它调用时,会把被访问的表和索引作为参数传入。从上面的例子来看,

我们可以使用带有条件语句的索引,以及任意的Lua语句。因此在这种情况下,如果索引和字符串"foo"

相等,我们可以返回0,否则,我们可以查询表中被使用的索引;当"foo"被使用时,让t作为table的

别名并返回0。(这句不是太懂,原文为:Therefore,inthatexample,ifthekeywasequalto

thestring"foo"wewouldreturn0,otherwisewelookupthetabletablewiththekeythat

wasused;thismakestanaliasoftablethatreturns0whenthekey"foo"isused.)

你可能会疑问,怎么把表作为是第一个传给__index函数的参数。当你在多个表里使用相同的元表时,

这会很方便,并支持代码复用和节省电脑资源。我们会在最下面的Vector类里看到解释。

--注:下面是我的一个例子

other=function(t,k)ifk=="foo"thenreturn0endend

t=setmetatable({},{__index=other})

print(t.foo)

__newindex

下一个是__newindex,它和__index类似。和__index一样,它可以包含函数和表。当你给表中不存在

的值赋值时,Lua会在metatable里查找__newindex,调用顺序和__index一样。如果__newindex是表,

索引和值会设置到指定的表:

other={}

t=setmetatable({},{__newindex=other})

t.foo=3--t里没有foo,查看__newindex,并把foo=3传给了other,并没有给t里的foo赋值

other.foo–3故为3

t.foo–nil故为nil

和期望的一样,__newindex是函数时,当被调用时会传递表、索引、值三个参数。

t=setmetatable({},{

__newindex=function(t,key,value)

iftype(value)=="number"then

rawset(t,key,value*value)

else

rawset(t,key,value)

end

end

})

t.foo="foo"

t.bar=4

t.la=10

t.foo--"foo"

t.bar--16

t.la--100

当在t里创建新的索引时,如果值是number,这个值会平方,否则什么也不做。下面介绍rawget和rawset。

rawget和rawset

有时需要get和set表的索引,不想使用metatable.你可能回猜想,rawget允许你得到索引无需__index,

rawset允许你设置索引的值无需__newindex(不,相对传统元表的方式,这些不会提高速度)。为了避免陷

在无限循环里,你才需要使用它们。在上面的例子里,t[key]=value*value将再次调用__newindex

函数,这让你的代码陷入死循环。使用rawset(t,key,value*value)可以避免。

你可能看到,使用这些函数,我们必须传递参数目标table,key,当你使用rawset时还有value。

操作符

许多元表的索引是操作符(如,+,-,等),允许你使用表完成一些操作符运算。例如,我们想要一个表支持

乘法操作符(*),我们可以这样做:

t=setmetatable({1,2,3},{

__mul=function(t,other),

new={}

fori=1,otherdo

for_,vinipairs(t)dotable.insert(new,v)end

end

returnnew

end

})

t=t*2--{1,2,3,1,2,3}

这允许我们创建一个使用乘法操作符重复某些次数的新表。你也看的出来,__mul和乘法相当的索引是,

与__index、__newindex不同,操作符索引只能是函数。它们接受的第一个参数总是目标表,接着

是右值(除了一元操作符“-”,即索引__unm)。下面是操作符列表:

__add:加法(+)

__sub:减法(-)

__mul:乘法(*)

__div:除法(/)

__mod:取模(%)

__unm:取反(-),一元操作符

__concat:连接(..)

__eq:等于(==)

__lt:小于(<)

__le:小于等于(<=)

(只有==,<,<=,因为你能通过上面的实现所有操作,事实上==和<就足够了)

__call

接下来是__call索引,它允许你把表当函数调用,代码示例:

t=setmetatable({},{

__call=function(t,a,b,c,whatever)

return(a+b+c)*whatever

end

})

t(1,2,3,4)–-24,表t在调用时先查找__call,调用里面的函数,t便相当于函数了

和通常一样在call里的函数,被传递了一个目标表,还有一些参数。__call非常有用,经常用来在表和它

里面的函数之间转发调用(原文it'susedforisforwardingacallonatabletoafunctioninside

thattable.)。kikito的tween.lua库就是个例子tween.start可以被自身调用(tween).另一个例子是

MiddleClass,类里的new函数可以被类自身调用。

__tostring

最后一个是__tostring。如果实现它,那么tostring可以把表转化为string,非常方便类似print的函数

使用。一般情况下,当你把表转为string时,你需要"table:0x<hex-code-here",但是你可以仅用

__tostring来解决。示例:

t=setmetatable({1,2,3},{

__tostring=function(t)

sum=0

for_,vinpairs(t)dosum=sum+vend

return"Sum:"..sum

end

})

print(t)--printsout"Sum:6"

创建一个向量类

下面我们来封装一个2D向量类(感谢hump.vector的大量代码)。代码太长你可以查看gist#1055480,

代码里有大量的metatable概念,(注意,如果你之前没接触面向对象可能会有点难)。

Vector={}

Vector.__index=Vector

首先声明了一个Vectorclass,设置了__index索引指向自身。这在干啥呢?你会发现我们把所有的元表

放到Vector类里了。你将看到在Lua里实现OOP(Object-OrientedProgramming)的最简单方式。Vector

表代表类,它包含了所有方法,类的实例可以通过Vector.new(如下)创建了。

functionVector.new(x,y)

returnsetmetatable({x=xor0,y=yor0},Vector)

end

它创建了一个新的带有x、y属性的表,然后把metatable设置到Vector类。我们知道Vector包含了所有的

元方法,特别是__index。这意味着我们通过新表可以使用所有Vector里方法。

另外重要的一行是:

setmetatable(Vector,{__call=function(_,...)returnVector.new(...)end})

这意味着我们可以创建一个新的Vector实例通过Vector.new或者仅Vector。

最后重要的事,你可能没注意冒号语法。当我们定义一个带有冒号的函数时,如下:

functiont:method(a,b,c)

--...

end

我们真正定义的是这个函数:

functiont.method(self,a,b,c)

--...

end

这是一个语法糖,帮助我们使用OOP。当调用函数时,我们可以这样使用冒号语法:

--thesearethesame

t:method(1,2,3)

t.method(t,1,2,3)

我们如何使用Vector类?示例如下:

a=Vector.new(10,10)

b=Vector(20,11)

c=a+b

print(a:len())--14.142135623731

print(a)--(10,10)

print(c)--(30,21)

print(a<c)--true

print(a==b)--false

因为Vector里有__index,我们可以在实例里使用它的所有方法。

结论

感谢阅读,我希望你学到了一些东西.如果你有建议或疑问,请留下评论commentssection,我想听到你的回复!

原文:http://www.cnblogs.com/xdao/archive/2013/04/02/lua-metatable.html

作者:半山

出处:http://www.cnblogs.com/xdao/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

lua

相关推荐