c++和java语言机制中本身带有面向对象的内容,而lua设计的思想是元编程,没有面向对象的实现。
但是利用lua的元表(matetable)机制,可以实现面向对象。要讲清楚怎样实现lua面向对象,需要讲清楚以下内容。
1.lua元表 2.类和对象 3.继承
1.lua元表
lua里的所有数据结构都是表。metatable可以改变table的行为。例如加法行为,table本身没有加法行为。
可以通过修改元表中的__add域,来提供表的加法行为。__add叫做元方法(metamethod)。
一个表默认是不带元表的。getmetatable可以获取元表。
t = {} print(getmetatable(t)) --nil
setmetatable可以设置元表。
t = {} setmetatable(t, t) --设置自己是自己的元表 print(t == getmetatable(t)) --true
任何一个表可以其他表的元表。一个表也可以是自己的元表。
来看一个展示__add元方法的例子。实现一个集合的并集运算。
Set = {} Set.mt = {} Set.new = function (t) local res = {} setmetatable(res, Set.mt) for k, v in pairs(t) do --用原始集合的value作为新集合的key res[v] = true --新集合的value值 可以是任意值 end return res end Set.union = function (a, b) local res = Set.new{} --res将Set.mt作为元表 for k, v in pairs(a) do res[k] = true end --res的所有key值作为新的并集,value值可以是任意值 for k, v in pairs(b) do res[k] = true end return res end Set.mt.__add = Set.union Set.print = function (t) local s = "{ " for k, v in pairs(t) do s = s .. tostring(k) .. " " --所有key值作为新的并集 end s = s .. "}" print(s) end s1 = Set.new{1, 2, 3, "hello"} s2 = Set.new{2, 3, 4, "world"} s3 = s1 + s2 Set.print(s3) --{ 1 2 3 4 hello world}
在new集合时将一个公共的mt表作为了集合的元表,那么通过new创建的集合都会有相同的元表,也就有了相同的行为。
再通过定义__add域,则改变了表的“+”行为,使得“+”变成了集合的并集运算。
可以重定义的元方法如下:
__add --加法 __sub --剑法 __mul --乘法 __div --除法 __unm --负 __pow --幂 __concat --连接 __eq --等于 __lt --小于 __le --小于等于 __tostring --字符串输出 __index --访问表的域 __newindex --更新表的域 __metatable --使元表不能被修改
其中最重要的__index,__newindex是用来实现面向对象的关键。下面一个对表的监控例子,可以看出__index,__newindex的作用。
original = {} --原始表 mt = {} mt.__index = function (t, k) --此表的访问操作,都会访问original表 print("access table element " .. tostring(k) .. " : " .. tostring(original[k])) return original[k] end mt.__newindex = function (t, k, v) --此表的赋值操作,都会操作original表 print("update table element " .. tostring(k) .. " : " .. tostring(original[k]) .. " to " .. tostring(v)) original[k] = v end t = {} --监控表 用来监控original setmetatable(t, mt) t[1] = "hello" --update table element 1 : nil to hello str = t[1] .. "world" --access table element 1 : hello print(str) --helloworld
当我们访问一个表的不存在的域时,会触发访问__index方法。当表缺少一个赋值域时,会触发访问__newindex方法。
这两个重要的元方法是实现面向对象的关键。
2.类和对象
我们创建一个对象作为其他对象的原型,当调用不属于该对象的方法时,会去原型中查找。
setmetatable(a, {__index = b})
则b是原型,a是原型的对象。概念上称b是类,a是b的实例对象。调用a中不存在的对象时,会去b中查找。
Cal = {} function Cal:New(o) o = o or {} setmetatable(o, self) self.__index = self return o end function Cal:Add(a, b) print("Cal Add") return a + b end a = Cal:New() print(a:Add(5, 6)) --11
调用Cal:New实际会返回一个以Cal自己为元表的新对象。而a中没有Add方法,则会在Cal中查找Add方法。
3.继承
有了类与对象,我们需要在此基础上实现继承。
Cal = {} function Cal:New(o) o = o or {} setmetatable(o, self) self.__index = self return o end function Cal:Add(a, b) print("Cal Add") return a + b end SubCal = Cal:New() function SubCal:Add(a, b) print("SubCal Add") return a + b end a = SubCal:New() print(a:Add(5, 6)) --11
一个SubCal子类,从基类Cal中继承了Add方法。a对象会首先在SubCal中查询方法,如果有则调用,因此子类可以复写父类的方法。如果没有则去到父类中查找方法。