function?Person?.?talk?(?self?,254)"> 这与上面的函数定义是等价的,但是这么写你就很难看出来?talk
?其实是Person
?表中的一个键,其对应的值为一个函数。
当然嘴巴都是长在自己身上的,说话只能自己说,不可能自己掌嘴别人说,所以每次都传个self参数实在是有点不美观,于是?冒号语法糖?上场。
我们还可以这么定义人类的说话功能:
function?Person?:?talk?(?words?)
print?(?self?.?name?.?.?"说:"?.?.?words?)
end
|
这与上面两段代码都是等价的,它的变化是少了?self
?的参数,将点Person.talk
?改为了冒号?Person:talk
?。
但是函数体内,却依然可以使用?self
?,在使用?:
?代替?.
?时,函数的参数列表的第一个参数不在是?words
?,Lua会自动将?self
?做为第一个参数。?这个?self
?参数代表的意思就是这个函数的实际调用者。
所以我们调用?Person:talk("你好")
?与?是等价的,这就是冒号语法糖带来的便利 性。
如何查找表中的元素?
下面我们需要理解在Lua的表中是怎么查找一个键所对应的值的。
假设我们要在表?p
?中查找?talk
?这个键所对应的值,请看下面的流程图:
p?中有没有?talk?这个键???有?--> 返回talk对应的值
|
没有
|
p?中是否设置过?metatable???否?-->??返回nil
|
有
|
在?p?的?metatable?中有没有?_?_?index这个键???没有?-->??返回nil
|
有
|???????
在?p?的?metatable?中的?_?_?index这个键对应的表中有没有?talk?这个键???没有?--> 返回nil
|
有,返回?getmetatable?(?p?)?.?__index?.?talk
|
理解以上内容是本文的重点,反复阅读直至你记住了。
可以看到,由于?metatable
?和?__index
?这两个神奇的东西,Lua能在当前表中不存在这个键的时候找到其返回值。
下面将会讲一讲?metatable
?这个语言特性。
对metatable的理解
metatable是什么?
metatable的中文名叫做元表。?它不是一个单独的类型,元表其实就是一个表。
我们知道在Lua中表的操作是有限的,例如表不能直接相加,不能进行比较操作等等。
元表的作用就是增加和改变表的既定操作。只有设置过元表的表,才会受到元表的影响而改变自身的行为。
通过全局方法?setmetatable(t,m)
?,会将表?t
?的元表设置为表?m
?。通过另一个全局方法?getmetatable(t)
?则会返回它的元表?m
?。
注意:所有的表都可以设置元表,然而新创建的空表如果不设置,是没有元表的。
元方法
元表作为一个表,可以拥有任意类型的键值对,其真正对被设置的表的影响是Lua规定的元方法键值对。
这些键值对就是Lua所规定的键,比如前面说到的?__index
?,?__add
?,__concat
?等等。这些键名都是以双斜杠?__
?为前缀。其对应的值则为一个函数,被称为元方法(metamethod),这些元方法定义了你想对表自定义的操作。
例如:前面所说的?__index
?键,在Lua中它所对应的元方法执行的时机是当查找不存在于表中的键时应该做的操作。考虑以下代码:
--定义元表m
m?=?{?}
--定义元表的__index的元方法
--对任何找不到的键,都会返回"undefined"
m?.?__index?=?function?(?table?,?key?)
return?"undefined"
end???
--表pos
pos?=?{?x?=?1?,?y?=?2?}
--初始没有元表,所以没有定义找不到的行为
--因为z不在pos中,所以直接返回nil
print?(?pos?.?z?)?-- nil
--将pos的元表设为m
setmetatable?(?pos?,?m?)
--这是虽然pos里仍然找不到z,但是因为pos有元表,
--而且元表有__index属性,所以执行其对应的元方法,返回“undefined”
print?(?pos?.?z?)?-- undefined
|
pos
?表中本没有?z
?这个键,通过设置?pos
?的元表为?m
?,并设置?m
?的__index
?对应的方法,这样所有取不到的键都会返回?“undefined”
?了。
以上我们了解到,?元表的?__index
?属性实际上是给表配备了找不到键时的行为。
注意:元表的?__index
?属性对应的也可以为一个表。
再举个栗子,希望能够加深对元表和元方法的理解,?__add
?键,考虑以下代码:
--创建元表m,其中有__add键和其定义的方法
local?m?=?{
__add?=?function?(?t1?,?t2?)
local?sum?=?{?}
for?key?,?value?in?pairs?(?t1?)?do
sum?[?key?]?=?value
end
for?key?,?value?in?pairs?(?t2?)?do
if?sum?[?key?]?then
sum?[?key?]?=?sum?[?key?]?+?value
else
sum?[?key?]?=?value
end
end
return?sum
end
}
--将table1和table2都设置为m
local?table1?=?setmetatable?(?{?10?,?11?,?12?}?,?m?)
local?table2?=?setmetatable?(?{?13?,?14?,?15?}?,?m?)
--表本来是不能执行 + 操作的,但是通过元表,我们做到了!
for?k?,?v?in?pairs?(?table1?+?table2?)?do
print?(?k?,?v?)
end
--print
--1 23
--2 25
--3 27
|
表本身是不能用?+
?连起来计算的,但是通过定义元表的?__add
?的方法,并setmetatable
?到希望有此操作的表上去,那些表便能进行加法操作了。
因为?元表的?__add
?属性是给表定义了使用+号时的行为。
类的实现手段
好,假设前面的内容你都没有疑问的阅读完毕话,我们开始进入正题。
请先独立思考一会,我们该怎么去实现一个Lua的类?
思考ing…
种种铺垫后,我们的类是一个表,它定义了各种属性和方法。我们的实例也是一个表,然后我们类作为一个元表设置到实例上,并设置类的?__index
?值为自身。
例如人类:
--设置Person的__index为自身
Person?.?__index?=?Person???
--p是一个实例
local?p?=?{?}
--p的元表设置为Person
setmetatable?(?p?,?Person?)
p?.?name?=?"路人甲"
--p本来是一个空表,没有talk这个键
--但是p有元表,并且元表的__index属性为一个表Person
--而Person里面有talk这个键,于是便执行了Person的talk函数
--默认参数self是调用者p,p的name属性为“路人甲”
p?:?talk?(?"我是路人甲"?)
--于是得到输出
--路人甲说:我是路人甲
|
为了方便,我们给人类一个创建函数?create
?:
function?Person?:?create?(?name?)
local?p?=?{?}
setmetatable?(?p?,?Person?)
p?.?name?=?name
return?p
end
local?pa?=?Person?:?create?(?"路人甲"?)
local?pb?=?Person?:?create?(?"路人乙"?)
pa?:?talk?(?"我是路人甲"?)?--路人甲说:我是路人甲
pb?:?talk?(?"我是路人乙"?)?--路人乙说:我是路人乙
|
这样我们可以很方便用Person类创建出pa和pb两个实例,这两个实例都具备Person的属性和方法。
原文:??http://wuzhiwei.net/lua_make_class/