17 Lua 面向对象
Lua中的table就是一种对象,看以下一段简单的代码:
local tb1 = {a = 1, b = 2}
local tb2 = {a = 1, b = 2}
local tb3 = tb1
if tb1 == tb2 then
print ("tb1 == tb2")
else
print ("tb1 ~= tb2")
end
tb3.a = 3
print (tb1.a)
上述代码会输出tb1 ~= tb2。说明两个具有相同值得对象是两个不同的对象,同时在Lua中table是引用类型的。我们是基于table来实现的模块,在table中可以定义函数,也就是说,每个table对象都可以拥有其自己的操作。看一段代码:
Account = {balance = 0}
function Account.withDraw(v)
Account.balance = Account.balance - v
end
Account.withDraw(10) --调用函数
print(Account.balance)
上面的代码创建了一个新函数,并将该函数存入Account对象的withDraw字段中,然后我们就可以调用该函数了。不过,在函数中使用全局名称Account是一个不好的编程习惯,因为这个函数只能针对特定对象工作,并且,这个特定对象还必须存储在特定的全局变量中。如果改变了对象的名称,withDraw就再也不能工作了。例如以下代码:
Account = {balance = 0}
function Account.withDraw(v)
Account.balance = Account.balance - v
end
Account.withDraw(10) --调用函数
print(Account.balance)
a = Account
Account = nil
a.withDraw(100)
-10
lua: test14_class.lua:23: attempt to index a nil value (global 'Account')
stack traceback:
test14_class.lua:23: in field 'withDraw'
test14_class.lua:32: in main chunk
[C]: in ?
这样就会出现错误。我在这里使用Account创建了一个新的对象a,当将Account赋值为nil时,应该要对a对象不产生任何影响。但是,由于在函数withDraw内部使用了Account,而不是变量a,所以就出现了错误。如果我们将withDraw函数内部的Account.balance = Account.balance – v语句修改为:a.balance = a.balance – v,这样就不会出现错误了。这就表明,当我们需要对一个函数进行操作时,需要指定实际的操作对象,即这里的a,这就需要一个额外的参数来表示该操作者,就好比C++中的this一样,只不过这里将这个关键字换成了self,换完以后的代码如下:
Account = {balance = 0}
function Account.withDraw(self, v)
self.balance = self.balance - v
end
a = Account
Account = nil
a.withDraw(a, 100)
print (a.balance)
这样再调用,就不会出现错误了。
使用self参数是所有面向对象语言的一个核心。大多数面向对象语言都对程序员隐藏了self参数,从而使得程序员不必显示地声明这个参数。Lua也可以,当我们在定义函数时,使用了冒号,则能隐藏该参数,那么上述代码使用冒号来改下,就是下面这个样子了。
Account = {balance = 0}
function Account:withDraw(v) --注意这里写的是":"
self.balance = self.balance-v
end
a = Account
Account = nil
a:withDraw(100) --注意这里调用时,也需要使用":"
print(a.balance)
冒号的作用很简单,就是在方法定义中添加一个额外的隐藏参数,以及在一个方法调用中添加一个额外的实参。冒号只是一种语法便利,并没有引入任何新的东西;如果你愿意,你可以可以不使用self,而是在每次定义一个函数时,手动的加上self,只要你处理好了self,它们都是一样的。
这里乱乱的讲了一些Lua中的东西,主要还是说了table是一个不一样的东西,还有self。接下来,就正式进入面向对象的世界。不要忘了,上面总结的东西是非常有用的。