使用元表实现 Lua 类 |
|
Lua 已从应用程序扩展语言发展成为一种灵活的脚本语言。Lua 5.0 并非像 Java 或 Ruby 那样的面向对象语言。相反,Lua 使您能够根据需要实现类。这既是优点也是缺点。高级用户喜欢这种自由,但新手有时会感到困惑。
元表是 Lua 为表添加魔法的方式。假设 t 是一个普通的表,如下所示
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio
> t = { 11, 22, 33, you='one', me='two' }
> table.foreach(t,print)
1 11
2 22
3 33
me two
you one
>
> = t[2]
22
> = t.me
two
> = t.fred
nil
使用有效索引对表进行索引将返回存储在该索引处的 value。
使用未定义索引对表进行索引将返回 nil。如果我们添加一些魔法,我们可以尝试索引另一个表,而不是返回 nil。我们甚至可以提供自己的函数来根据需要处理未定义的索引。
这些自定义某些 Lua 行为的函数在 Lua 的第一个版本中被称为回退。在 Lua 4.0 中,它们被称为标签方法。现在在 Lua 5.0 中(主要归功于 Edgar Toernig),这些函数被称为元方法,它们存储在称为元表的表中。
我们可以自定义的行为具有特殊的名称,被称为事件。向表添加新索引称为newindex 事件。尝试从表中读取未定义的索引称为index 事件。
为了查看访问未定义索引时会发生什么,让我们打印出传递给index 事件元方法的参数。
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio
> t = { 11, 22, 33, you='one', me='two' }
> mt = { __index = print }
> = t.you
one
> = t.fred
nil
> setmetatable(t, mt)
> x = t.fred
table: 0x8075e80 fred
> = x
nil
> = t
table: 0x8075e80
>
请注意,第一个参数是表 t,第二个参数是索引 fred。
如果我们对newindex 事件执行相同的操作,我们会看到有一个第三个参数,它是存储在索引处的新的 value。
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio
> t = { 11, 22, 33, you='one', me='two' }
> mt = { __newindex = print }
> setmetatable(t, mt)
> t[4] = 'rat'
table: 0x8075e80 4 rat
>
如前所述,我们可以指定一个表而不是函数,并且将访问该表。
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio
> t = { 11, 22, 33, you='one', me='two' }
> s = { }
> mt = { __newindex = s, __index = _G }
> setmetatable(t, mt)
> = t.you
one
> x = 'wow'
> = t.x
wow
> t[5] = 99
> table.foreach(s, print)
5 99
>
以下展示了如何实现向量类。我们有一个用于方法的表,一个元表。每个对象都有一个额外的表。所有对象共享相同的方法表和相同的元表。
请记住,v1:mag() 类似于 v1.mag(v1),因此 Lua 尝试在 v1 中查找 mag,这将触发 index 事件,然后在表 Vector 中查找 mag。
Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio
> Vector = {}
> Vector_mt = { __index = Vector }
>
> function Vector:new(x,y)
>> return setmetatable( {x=x, y=y}, Vector_mt)
>> end
>
> function Vector:mag()
>> return math.sqrt(self:dot(self))
>> end
>
> function Vector:dot(v)
>> return self.x * v.x + self.y * v.y
>> end
>
> v1 = Vector:new(3,4)
> table.foreach(v1,print)
y 4
x 3
> = v1:mag()
5
> v2 = Vector:new(2,1)
> = v2:dot(v1)
10
>
> = Vector
table: 0x8076028
> table.foreach(Vector,print)
mag function: 0x8078008
dot function: 0x8078b58
new function: 0x80773e8
> = v1, v2
table: 0x8079110 table: 0x8079a80
> = Vector_mt, getmetatable(v1), getmetatable(v2)
table: 0x80763b8 table: 0x80763b8 table: 0x80763b8
> table.foreach(Vector_mt,print)
__index table: 0x8076028
>
如果你想要一个默认构造函数和一个复制构造函数,你可以创建一个名为Class.lua的文件,内容如下:
function Class(members) members = members or {} local mt = { __metatable = members; __index = members; } local function new(_, init) return setmetatable(init or {}, mt) end local function copy(obj, ...) local newobj = obj:new(unpack(arg)) for n,v in pairs(obj) do newobj[n] = v end return newobj end members.new = members.new or new members.copy = members.copy or copy return mt end
然后将我们的 Vector 类放在名为Vec.lua的文件中。
require'Class' Vector = {} local Vector_mt = Class(Vector) function Vector:new(x,y) return setmetatable( {x=x, y=y}, Vector_mt) end function Vector:mag() return math.sqrt(self:dot(self)) end function Vector:dot(v) return self.x * v.x + self.y * v.y end
然后像下面这样测试它:
$ lua -lVec -i -v Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio > v1 = Vector:new(3,4) > table.foreach(v1,print) y 4 x 3 > = v1:mag() 5 > v2 = Vector:new(2,1) > = v2:dot(v1) 10 > > table.foreach(Vector,print) copy function: 0x80692c0 dot function: 0x8069300 mag function: 0x80692e0 new function: 0x8069398 > > v3 = v1:copy() > = v1, v2, v3 table: 0x80779d0 table: 0x8078428 table: 0x807a050 > table.foreach(v1,print) y 4 x 3 > table.foreach(v3,print) y 4 x 3 >
如果我们将Class函数应用于 Lua 的 table 库,我们可以创建 table 对象。
require'Class' Class(table) function table:push(x) assert( x ~= nil, 'will not push nil into table') self:insert(x) return self, x end function table:map(func, ...) local R = table:new{} for name,value in pairs(self) do func(R,name,value,unpack(arg)) end return R end function table:imap(func, ...) local R = table:new{} for index,elem in ipairs(self) do func(R,index,elem,unpack(arg)) end return R end
然后你就不必再输入table.foreach或table.getn(t)了。
$ lua -lTable -i -v Lua 5.0.3 Copyright (C) 1994-2006 Tecgraf, PUC-Rio > t = table:new{ 11, 22, 33, you='one', me='two' } > = t:getn() 3 > t:foreach(print) 1 11 2 22 3 33 me two you one > > = t:concat',' 11,22,33 > = table table: 0x8067808 > = getmetatable(t) table: 0x8067808 > > s = t:copy() > s:foreach(print) 1 11 2 22 3 33 me two you one > = s, t table: 0x8079a58 table: 0x8077bb8 >