Lua面向对象程序设计
Lua中的表某种意义上可以说是一种对象。如如下代码:
local TA = {a = 1, b = 1} local TB = {a = 1, b = 1} local TC = TA if TA == TB then print("TA == TB") else print("TA ~= TB") end print(TC.a) TC.a = 3 print(TC.a)
运行得到结果为TA ~= TB,由此我们可以知道虽然TA、TB具有相同的值,但他们是不同的对象。由print(TC.a)为3可知道Lua中的table为引用类型。
接着看另一段Lua代码:
Account = {blance = 0} function Account.withDraw(v) Account.blance = Account.blance - v end <pre style="margin: 4px 0px; line-height: 21px; background-color: rgb(240, 240, 240);" name="code">Account.withDraw(10) print(Account.blance) --体会下blance的作用 这个定义了创建了一个新的函数,Account.withDraw(10)则是我们的调用方法。然而这种做法的弊端则是函数内部使用全局变量Account,如果全局变量的名称发生了变化,那么调用方法也要做出相应的改变。比如:
a = Account Account = nil a.withDraw(10) print(a.blance)结果会出现错误,我们使用Account创建了一个对象a,当Account = nil时对a并没有什么影响,然而由于函数withDraw在内部使用了Account而不是a,所以会导致出错。需将函数内部改为a.blance = aa.blance - v才可以。所以我们可以发现当我们对函数进行操作时,需要指定的实际的操作对象。所以在定义函数时,我们可以定义一个额外的参数,来表示方法作用的对象,就想是C++中的this,而Lua中通常用self来表示:
<pre style="margin: 4px 0px; line-height: 21px; background-color: rgb(240, 240, 240);" name="code"> Account = {blance = 0} function Account.withDraw(self,v) --等同于 Account:withDraw(v) 即可以把self省略,注意换成了冒号 self.blance = self.blance - v end a = Account Account = nil a.withDraw(a, 10) --等同于a:withDraw(10) print(a.blance)
类
Lua中的类更贴切的叫法应该为原型,在C++中,类的对象应进行实例化(除了静态成员),然而在Lua中,类可以不进行实例化。在Lua中,要表示一个类,只需创建一个专做其他对象的原型,也就是说我们可以直接通过原型去调用对应的方法。当对象没有找到对应的操作时,则会在原型中进行查找。
在Lua中,原型的实现非常简单,比如有两个对象a和b,如要要b作为a的原型,则有
setmetatable(a,{__index = b})
设置了这段代码后,a便会在b中才找它没有的操作,某种意义上说b为a的类。要创建一个实例对象,必须要有一个原型,如:
local Account= {} --创建一个原型
对原型进行实例化:
function Account:new(o)
o = o or {} --如果为nil table,给o创建一个
setmetatable(o,self) --Account作为o的原型
self.__index = self
return o
end
当调用Account时,相当于调用self:
local a = Account:new(value = 100) --使用原型Account创建了一个对象a
现在我们对该原型进行修改:
local Account = {value = 0}
function Account:new(o)
o = o or {} --如果为nil table,给o创建一个
setmetatable(o,self) --Account作为o的原型
self.__index = self
return o
end
function Account:display()
self.value = self.value + 100
print(self.value)
end
local a = Account:new()
a:display()
a:display()
输出结果为100,200.当我创建了实例对象a时,并没有提供value字段,在display函数中,由于a中没有value字段,就会查找元表Account,最终得到了Account中value的值,等号右边的self.value的值就来源自Account中的value。调用a:display()时,其实就调用以下代码:
a.display(a)
在display中定义则变成了:
a.value = getmetatable(a).__index(value) + 100
第一次调用display时,等号左侧的self.value就是a.value,就相当于在a中添加了一个新的字段value;当第二次调用display函数时,由于a中已经有了value字段,所以就不会去Account中寻找value字段了。
继承
沿用上面代码,稍微修改后得:
local Account = {value = 0}
function Account:new(o)
o = o or {} --如果为nil table,给o创建一个
setmetatable(o,self) --Account作为o的原型
self.__index = self
return o
end
function Account:display(v)
self.value = self.value + v
print(self.value)
end
现从类Account派生出一个子类ASon:
local ASon = Account:new()
ASon既是一个类,又是一个对象,相当于ASon继承Account,再如如下代码:
local s = ASon:new{value1 = 10}
ASon从Account继承了new;不过,在执行ASon:new时,它的self参数表示为ASon,所以s的元表为ASon,ASon中字段__index的值也是ASon。然后,我们就会看到,s继承自ASon,而ASon又继承自Account。当执行s:display时,Lua在s中找不到display字段,就会查找ASon;如果仍然找不到display字段,就查找Account,最终会在Account中找到display字段。可以这样想一下,如果在ASon中存在了display字段,那么就不会去Account中再找了。所以,我们就可以在ASon中重定义display字段,从而实现特殊版本的display函数。