最近又开始折腾Lua了,说实话以前没有把Lua看成什么高深的语言,一直把他当做是配合宿主程序的所谓的“脚本”(虽然事实的确如此),不过最近看了一些Lua代码才发现,原来Lua可以通过很简单巧妙的变化,模拟出一些其他语言引以为傲的特性,真没想到Lua还可以这样玩,哈哈。
目前主流的编程语言C++/C#/Java都是面向对象的语言,面向对象符合我们正常的思维观念,面向对象的特性,封装,继承等更是可以大大的帮助我们提升编程的效率。比如我们要写一系列的人物对象,每个对象的基础方法都相同,只是有一些特有的方法和字段,如果没有面向对象,我们可能就要“Ctrl+C”,"Ctrl+V"了,这可是写代码的大忌啊。所以机智的先人们早就想好了怎么让Lua面向对象了。
不过在学习怎么让Lua面向对象之前,还要学习一点预备知识,因为这个东东才是让Lua能够面向对象的秘密武器。
元表是Lua中很重要的一个概念,所谓元表(metatable),就是对应一个类型最基本的一些方法的集合,这些方法叫做元方法(metamethod)。
举个例子来说明一下。比如+方法,我们给两个数进行加法运算,系统就会自己调用数的__Add元方法进行加法操作。我们并没有定义这个方法,
但是Lua会去数的元表里找到_Add进行加法运算。
Lua中,我们定义一个table时,是不会自动生成元表的。
_G.Base = {}
Base.name = "Base"
function Test()
print(getmetatable(Base));
end
Test()
结果:
nil
我们可以通过setmetatable方法来给table设置元表。看一个例子:
Set = {}
--创建新的Set
function Set.new(l)
local set = {}
for _,v in ipairs(l) do
set[v] = true
end
return set
end
--打印传入的Set
function Set.Print(s)
for e in pairs(s) do
print(tostring(e))
end
end
--Union方法,合并两个Set
function Set.Union(a,b)
local newSet = {}
for e in pairs(a) do newSet[e] = true end
for e in pairs(b) do newSet[e] = true end
return newSet;
end
s1 = Set.new{1,2,3}
s2 = Set.new{4,5,6}
Set.Print(s1) --输出1,2,3
Set.Print(s2) --输出4,5,6
newS = Set.Union(s1,s2)
Set.Print(newS) --输出1,2,3,4,5,6
这是一个合并两个集合的代码,调用方式比较麻烦,有没有类似C++运算符重载的那种操作呢?这就要用到传说中的元表了。只要加上这样几句话,我们就可以实现“+”运算符把集合取并集的功能啦:
--元表
local mt = {}
--元表的Add方法(+运算符)设置为Set.Union方法
mt.__add = Set.Union
--创建新的Set
function Set.new(l)
local s = {}
--在要返回的s上设置元表
setmetatable(s, mt)
for _,v in ipairs(l) do
s[v] = true
end
return s
end
--可以这样调用啦
newSet = s1 + s2
Set.Print(newSet) --输出1,2,3,4,5,6
当然,元方法不止这么多,类似的,我们还可以实现__sub,__tostring,__index等等元方法。
--直接输出
Set1 = {}
print(Set1.hello); --输出nil
--只设置元表
Set2 = {}
Set2.mt2 = {}
function Set2.new()
local o = {}
setmetatable(o, Set2.mt2)
return o
end
so2 = Set2.new()
print(so2.hello) --虽然设置了元表,但是没有设置__index元方法,所以还是nil
--设置元表和__index元方法
Set3 = {}
Set3.mt3 = {}
function Set3.func()
return "I am meta method!"
end
Set3.mt3.__index = Set3.func
function Set3.new()
local o = {}
setmetatable(o,Set3.mt3)
return o
end
so3 = Set3.new()
print(so3.hello) --输出I am meta method! 有元方法__index,就返回index所指向的内容
--设置元表和__index元方法,此处__index指向了一个含有hello的表
Set4 = {}
Set4.mt4 = {}
Set4Base = {}
Set4Base.hello = "I am base, I have hello"
Set4.mt4.__index = Set4Base
function Set4.new()
local o = {}
setmetatable(o,Set4.mt4)
return o
end
so4 = Set4.new()
print(so4.hello) --输出I am base, I have hello 有元方法__index,且index指向了一个表,在这个表中找到了hello字段
通过上面的实验,我们看出,__index元方法确实比较强大,可以让我们在找不到内容的时候将内容指向我们希望返回的函数,值,或者table。
Set = {}
Set.hello = "1"
Set.world = "2"
--这个s参数就是要被操作的Set对象
function Set.Get1(s, key)
return s[key]
end
--不过Lua还有另外一种方式,简化了函数的定义和调用
--这种方式使用:来定义和调用函数。相当于把对象本身传递进去
function Set:Get2(key)
return self[key]
end
--把对象本身也传递进去
print(Set.Get1(Set, "hello")) --输出1
--省略了对象自己,有木有像C++那样默认传递this指针,注意要用:调用!
print(Set:Get2("hello")) --输出1
Set = {}
--传说中的new方法,自己创建一个local对象返回回去就是新对象啦!
function Set:new(name)
local o = {}
o.name = name
return o
end
set1 = Set:new("set1")
set2 = Set:new("set2")
print(set1.name)--输出set1
print(set2.name)--输出set2
我们看到,通过我们Set类的new方法,创建了两个不同的对象,每个对象内部都封装了自己的name字段。
Set = {}
--传说中的new方法,自己创建一个local对象返回回去就是新对象啦!
function Set:new(name)
local o = {}
o.name = name
return o
end
--把对象本身传入的方法
function Set.printName1(s)
print(s.name)
end
--使用self省略对象本身的方法
function Set:printName2()
print(self.name)
end
set1 = Set:new("set1")
set2 = Set:new("set2")
--直接使用Set方法调用(有木有点像C++的Static方法?)
Set.printName1(set1) --输出set1
--下面的会报错
--set1.printName1(set1)--找不到printName1方法
--set2.printName1(set2)--找不到printName1方法
--set1:printName2()--找不到printName2方法
--set2:printName2()--找不到printName2方法
--没有定义Set对象本身的name字段,所以是nil
Set:printName2() --nil
上一大节说到,__index元方法如果指向了一个table,在表本身找不到相应字段的时候,就会去该table中找。想想看,这个table像什么?对了,基类!我们把子类的元表的__index方法设置成基类的表。这样,在我们找一个方法找不到的时候,就会去相应的基类去寻找,这正是面向对象继承的特性。
废话多说,上代码:
Set = {}
--Set类的元表mt
Set.mt = {}
--设置元表的__index元方法为Set本身,这样在子类找不到方法定义时,会到Set类去找方法定义
Set.mt.__index = Set
--传说中的new方法,自己创建一个local对象返回回去就是新对象啦!
function Set:new(name)
local o = {}
o.name = name
--给new粗来的对象设置元表
setmetatable(o, Set.mt)
return o
end
--把对象本身传入的方法
function Set.printName1(s)
print(s.name)
end
--使用self省略对象本身的方法
function Set:printName2()
print(self.name)
end
set1 = Set:new("set1")
set2 = Set:new("set2")
--直接使用Set方法调用(有木有点像C++的Static方法?)
Set.printName1(set1) --输出set1
--下面的会报错
set1.printName1(set1)--set1
set2.printName1(set2)--set2
set1:printName2()--set1
set2:printName2()--set2
--没有定义Set对象本身的name字段,所以是nil
Set:printName2() --nil
set1,set2对象虽然没有定义printName1,2方法,但是他们设置了元表,元表的__index方法设置了找不到方法时指向的Set对象。调用时如果本类中没有该方法,就会去基类Set中找这个方法,这样就实现了继承!!
我们如果不想要基类的方法,也可以覆盖掉这个方法:
set1 = Set:new("set1")
set2 = Set:new("set2")
--覆写set1对象的printName2方法,注意:要在set1创建之后!不然会找不到set1的
function set1:printName2()
print("haha")
end
--会调用被覆写了的函数
set1:printName2()--haha