最近又开始折腾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