lua中的表及面向对象
在lua中,只有唯一的一种数据结构,表。通过表,却可以用来实现类一样的形式,其关键是对于表的元方法的使用,可以有很多奇妙的用处。
Lua中,所有的值都可以有一个有一个元表(metatable),元表中的元素定义了对于值特定操作的方法,就叫做元方法。当然,这些元表是有默认值的,但我们可以通过改变值元表中的元方法来进行特定操作行为的改变。
可以通过setmetatable()
函数来修改一个表的元表,而其他类型的值元表只能通过 C API来改变。
默认情况下,值是没有元表的,所以我们手动去设置,但 string库为字符串类型设置了一个元表。
表table
lua中没有复杂的数据结构,只有表。通过代码:
t = {} |
就建立了一个表。可以验证,其确实是没有元表的:
print(getmetatable(t)) |
getmetatable()
用来获取一个表的元表。
输出将是nil。而对于字符串:
print(getmetatable("hello world") |
其输出会是类似table: 0x ...
这样。表示其有一个元表。
元表的元素
一个完整的元表,看起来应该是这样的:
mt = { "__ev" = method, ... } |
其中ev可以是,__add, _sub, __mul, __div, __mod, __pow, __unm, __idiv, __band, __bor, __bxor, __bnot, __shl, __shr, __concat, __len, __eq, __lt, __le, __index, __newindex。
这里面的键被成为事件。
__index事件
我们重点关注一下__index
这个事件。
假如我们有一个表 t = {1, x = 2, y = 3,}
,那么,t.x 与 t[“x”]的值应该是一样的:
t = {1, x = 2, y = 3,} |
以 键 作为索引访问表的元素时,这是正确的。但如果是整数作为索引来访问表元素的话,这就是不对的。t[1],不会等于 t.1,而会出现一个错误。
我们特意用t.n来访问一个表中不存在的元素,很明显,其输出是nil。
我们现在来看一下,官方对于 __index
事件的说明:
索引访问table[key]。这个事件会在table不是一个表,或key在表中不存在的时候发生。
元方法可以是函数和一个表。如果是函数,以table, key作为参数调用这个元方法,函数返回的结果,就是这个索引访问操作的结果;如果元方法是一个表,那么就以key来索引访问这个 作为元方法的表 中元素。(这个索引访问走的常规流程,也有可能引发另外一次元方法的调用)
我们现在给表t,设置一个元表mt,mt内定义了__index
的元方法。
t = {1, x = 2, y = 3,} |
输出将是:
this key is not here
由于访问了不存在的索引,所以触发了__index
事件。
现在,我们把mt中__index
的元方法设置为一个表:
t = {1, x = 2, y = 3, func = print} |
输出将是:
------------ |
当元方法是一个函数时,索引访问的结果,是元方法调用的结果,同时不存在键不会被加上;而当元方法是一个表时,会从作为元方法的那个表内取出对应的值来 加到当前表上。
到这里,想必你已经发现了什么。
函数是匿名的
Lua中,所有的函数都是匿名的。
function (v) |
其实与:
foo = function (v) return v end |
是等价的。
调用函数的时候,括号是必须的。但在只有一个参数,且参数是字符串或表的时候可以省略。
也就是说:
print("hello world") 与 print "hello world"
等价。
表中的函数
所以:
|
后面对t.func进行赋值的两种形式是等价的,但第一中形式看起来会更加易读一些。
面向对象
表也是一个对象。一个对象,简单来说,会具有状态(属性),方法,可以通过方法来改变自身状态等等。
我们来假设一个钱包的情况。
wallet = { remain = 0 } |
输出是什么?
0 |
wallet.pay()调用影响了a的值,这说明Lua中,值存储于内存中,变量只是对其的一个引用。
我们再来看另外一个问题:
wallet = nil |
输出是:
attempt to index a nil value (global 'wallet')
我们销毁了wallet变量,这个时候a也无法工作了。这是因为在pay调用中,所操作的对象是wallet。 而我们需要的,是操作a本身。
在面向对象的概念中,一个对象,调用方法,叫做向这个对象发送消息,换言之,对象就是消息的接收着。
在上面的例子者,消息的接收者是a,而操纵的对象却是wallet,这是一种非常不好的做法,我们也应在 方法的内部去 操纵全局变量。
我们需要的,其实是一种操纵消息接收者自身的机制。幸好,Lua提供了这个机制,通过:
冒号来调用方法,即可在方法内部使用self这个代表自身的对象。
将上面的代码进行修改:
wodediannaodeMacBook-Air:lua shouzheng.zhang$ lua 1.lua |
如此,通过:
调用方法(函数),就少了这么多的麻烦事情了。
继承
回到前面那个问题,wallet和a引用的对象都是一样的,所以会造成相互调用间出现影响的情况。
而通过 __index事件一节我们看到,对于 一个表 t,中不存在的元素,其会通过其 元表t.mt中 __index
事件的元方法来寻找。而当 元方法是个表时, 还会直接通过 元方法表 中对应的索引值 来初始化自身表内的 键-值对。
那么,我们可以通过把一个 表 a 作为表 b 的__index
元方法表,这样,b就能 继承到 a 的所有元素。
a = { remain = 0 } |
这里,我们可以把a 看成一个类,其方法 new()
创建表o,并把表 a 作为其 元表,同时把 元表 __index
事件的值设置为 a,就可以让 o从 a 取得任何其不具有的元素。