加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

lua元表

发布时间:2020-12-14 22:06:40 所属栏目:大数据 来源:网络整理
导读:lua元表 本文简译自一篇老外的博客,写得不错可惜我翻译的太烂,简译如下。 (key--value常见翻译为“键值对”,我翻译为索引、值) 在这篇教程里我会介绍Lua中一个重要的概念: metatable(元表),掌握元表可以让你更有效的 使用Lua。 每一个tabel都可以附

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)
    if key == "foo" then
      return 0
    else
      return table[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,in that example,if the key was equal to

the string "foo" we would return 0,otherwise we look up the?table?table with the key that

was used; this makes?t?an alias of?table?that returns 0 when the key "foo" is used.)

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

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

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

other = function(t,k) if k=="foo" then return 0 end end?
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)?
??? if type(value) == "number" then?
????? rawset(t,value * value)?
??? else?
????? rawset(t,value)?
??? end?
? end?
})?

t.foo = "foo"?
t.bar = 4?
t.la = 10?
t.foo -- "foo"?
t.bar -- 16?
t.la -- 100?

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

?

rawget?和?rawset

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

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

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

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

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

?

操作符

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

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

t = setmetatable({ 1,2,3 },{?
? __mul = function(t,other),?
??? new = {}?
????
??? for i = 1,other do?
????? for _,v in ipairs(t) do table.insert(new,v) end?
??? end?
????
??? return new?
? end?
})?

t = t * 2 -- { 1,3,1,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,4) –- 24 ,表t在调用时先查找__call,调用里面的函数,t便相当于函数了

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

里面的函数之间转发调用(原文it's used for is forwarding a call on a table to a function inside

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

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

?

__tostring

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

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

__tostring来解决。示例:

t = setmetatable({ 1,{
  __tostring = function(t)
    sum = 0
    for _,v in pairs(t) do sum = sum + v end
    return "Sum: " .. sum
  end
})

print(t) -- prints out "Sum: 6"

?

创建一个向量类

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

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

Vector = {}
Vector.__index = Vector

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

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

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

function Vector.new(x,y)
  return setmetatable({ x = x or 0,y = y or 0 },Vector)
end

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

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

另外重要的一行是:

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

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

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

function t:method(a,c)
  -- ...
end

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

function t.method(self,c)
  -- ...
end

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

-- these are the same
t:method(1,3)
t.method(t,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,我们可以在实例里使用它的所有方法。

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读