使用元表实现 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 >