在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
原文地址 http://wuzhiwei.net/lua_make_class/ 不错,将metatable讲的很透彻,我终于懂了。 ------------------------------------------------------------
Lua中没有 下文将详细的解释在Lua中实现类的原理,涉及到的细节点将拆分出来讲,相信对Lua中实现类的理解有困难的同学将会释疑。 类是什么?想要实现类,就要知道类到底是什么。 在我看来,类,就是一个自己定义的变量类型。它约定了一些它的属性和方法,是属性和方法的一个集合。 所有的方法都需要一个名字,即使是匿名函数实际上也有个名字。这就形成了方法名和方法函数的键值映射关系,即方法名为键,映射的值为方法函数。 比如说有一个类是人,人有一个说话的方法,那就相当于,人(Person)是一个类,说话(talk)是它的一个方法名,说话函数是它的实际说话所执行到的内容。 人也有一个属性,比如性别,性别就是一个键(sex),性别的实际值就是这个键所对应的内容。 理解了类实际上是一个键值对的集合,我们不难想到用Lua中自带的表来实现类。 实例是什么?如果理解了类实际就是一个键值映射的表,那么我们再来理解实例是什么。 实例就是具有类的属性和方法的集合,也是一个表了。听起来好像和类差不多? 类全局只有一个集合,相当于上帝,全局只有一块内存;而实例就普通了,普天之下有那么多人,你可以叫A说一句话,A便执行了他的说话方法,但是不会影响B的说话。因为他们是实例,彼此分配着不同的内存。 说了那么多废话,其实实例就是由类创建出来的值,试着把类想象成类型而不是类。 两个语法糖试着创建一个人类 Person = {name="这个人很懒"}
以上代码将 说成白话就是人类拥有一个叫名字的属性。 那就再赋予人类一个说话的功能吧。 Person.talk = function(self, words) print(self.name.."说:"..words) end 以上代码在 好了,只要调用, 不过在写程序时,大家都习惯把 function Person.talk(self, words) print(self.name.."说:"..words) end
这与上面的函数定义是等价的,但是这么写你就很难看出来 当然嘴巴都是长在自己身上的,说话只能自己说,不可能自己张嘴别人说话,所以每次都传个self参数实在是有点不美观,于是冒号语法糖上场。 我们还可以这么定义人类的说话功能: function Person:talk(words) print(self.name.."说:"..words) end
这与上面两段代码都是等价的,它的变化是少了 但是函数体内,却依然可以使用 所以我们调用 如何查找表中的元素?下面我们需要理解在Lua的表中是怎么查找一个键所对应的值的。 假设我们要在表 p中有没有talk这个键? 有 --> 返回talk对应的值 | 没有 | p中是否设置过metatable? 否 --> 返回nil | 有 | 在p的metatable中有没有__index这个键? 没有 --> 返回nil | 有 | 在p的metatable中的__index这个键对应的表中有没有talk这个键? 没有 --> 返回nil | 有,返回getmetatable(p).__index.talk
理解以上内容是本文的重点,反复阅读直至你记住了。 可以看到,由于 下面将会讲一讲 对metatable的理解metatable是什么?metatable的中文名叫做元表。它不是一个单独的类型,元表其实就是一个表。 我们知道在Lua中表的操作是有限的,例如表不能直接相加,不能进行比较操作等等。 元表的作用就是增加和改变表的既定操作。只有设置过元表的表,才会受到元表的影响而改变自身的行为。 通过全局方法 注意:所有的表都可以设置元表,然而新创建的空表如果不设置,是没有元表的。 元方法元表作为一个表,可以拥有任意类型的键值对,其真正对被设置的表的影响是Lua规定的元方法键值对。 这些键值对就是Lua所规定的键,比如前面说到的 例如:前面所说的 --定义元表m m = {} --定义元表的__index的元方法 --对任何找不到的键,都会返回"undefined" m.__index = function ( table, key ) return "undefined" end --表pos pos = {x=1, y=2} --初始没有元表,所以没有定义找不到的行为 --因为z不在pos中,所以直接返回nil print(pos.z) -- nil --将pos的元表设为m setmetatable(pos, m) --这是虽然pos里仍然找不到z,但是因为pos有元表, --而且元表有__index属性,所以执行其对应的元方法,返回“undefined” print(pos.z) -- undefined
以上我们了解到,元表的 注意:元表的 再举个栗子,希望能够加深对元表和元方法的理解, --创建元表m,其中有__add键和其定义的方法 local m = { __add = function(t1, t2) local sum = {} for key, value in pairs(t1) do sum[key] = value end for key, value in pairs(t2) do if sum[key] then sum[key] = sum[key] + value else sum[key] = value end end return sum end } --将table1和table2都设置为m local table1 = setmetatable({10, 11, 12}, m) local table2 = setmetatable({13, 14, 15}, m) --表本来是不能执行 + 操作的,但是通过元表,我们做到了! for k, v in pairs(table1 + table2) do print(k, v) end --print --1 23 --2 25 --3 27
表本身是不能用 因为元表的 类的实现手段好,假设前面的内容你都没有疑问的阅读完毕话,我们开始进入正题。 请先独立思考一会,我们该怎么去实现一个Lua的类?
种种铺垫后,我们的类是一个表,它定义了各种属性和方法。我们的实例也是一个表,然后我们类作为一个元表设置到实例上,并设置类的 例如人类: --设置Person的__index为自身 Person.__index = Person --p是一个实例 local p = {} --p的元表设置为Person setmetatable(p, Person) p.name = "路人甲" --p本来是一个空表,没有talk这个键 --但是p有元表,并且元表的__index属性为一个表Person --而Person里面有talk这个键,于是便执行了Person的talk函数 --默认参数self是调用者p,p的name属性为“路人甲” p:talk("我是路人甲") --于是得到输出 --路人甲说:我是路人甲
为了方便,我们给人类一个创建函数 function Person:create(name) local p = {} setmetatable(p, Person) p.name = name return p end local pa = Person:create("路人甲") local pb = Person:create("路人乙") pa:talk("我是路人甲") --路人甲说:我是路人甲 pb:talk("我是路人乙") --路人乙说:我是路人乙 这样我们可以很方便用Person类创建出pa和pb两个实例,这两个实例都具备Person的属性和方法。 -----------------------------很久以后加的评论:(class是cocos2dx的framework里面提供的一个方法,在functions.lua里面,直接传入子类和父类即可。)------------- 这篇文章非常有助于对metatable的理解。但是,我个人觉得,实现类,用metatable显的太复杂,lua中直接用class实现更清晰。 比如 我定义一个类Person local Person = class("Person") function Person:ctor() self.name = "这个人很懒" end function Person:talk( words) print(self.name.."说:"..words) end return Person 定义好以后,这样调用 import("..module.Person") -- 首先要引入Person类,看你的路径修改 local pb= Person.new() 我觉得这样比metatable更加清晰明了,你觉得呢。 |
请发表评论