原文地址 http://wuzhiwei.net/lua_make_class/函数
不错,将metatable讲的很透彻,我终于懂了。lua
------------------------------------------------------------spa
Lua中没有类
的概念,但咱们能够利用Lua自己的语言特性来实现类
。.net
下文将详细的解释在Lua中实现类的原理,涉及到的细节点将拆分出来说,相信对Lua中实现类的理解有困难的同窗将会释疑。3d
想要实现类,就要知道类究竟是什么。code
在我看来,类,就是一个本身定义的变量类型。它约定了一些它的属性和方法,是属性和方法的一个集合。blog
全部的方法都须要一个名字,即便是匿名函数实际上也有个名字。这就造成了方法名和方法函数的键值映射关系,即方法名为键,映射的值为方法函数。seo
好比说有一个类是人,人有一个说话的方法,那就至关于,人(Person)是一个类,说话(talk)是它的一个方法名,说话函数是它的实际说话所执行到的内容。内存
人也有一个属性,好比性别,性别就是一个键(sex),性别的实际值就是这个键所对应的内容。get
理解了类其实是一个键值对的集合,咱们不难想到用Lua中自带的表来实现类。
若是理解了类实际就是一个键值映射的表,那么咱们再来理解实例是什么。
实例就是具备类的属性和方法的集合,也是一个表了。听起来好像和类差很少?
类全局只有一个集合,至关于上帝,全局只有一块内存;而实例就普通了,普天之下有那么多人,你能够叫A说一句话,A便执行了他的说话方法,可是不会影响B的说话。由于他们是实例,彼此分配着不一样的内存。
说了那么多废话,其实实例就是由类建立出来的值,试着把类想象成类型而不是类。
试着建立一我的类 Person
Person = {name="这我的很懒"}
以上代码将Person
初始化为一个表,这个表拥有一个为name
的键,其默认值是"这我的很懒"
。
说成白话就是人类拥有一个叫名字的属性。
那就再赋予人类一个说话的功能吧。
Person.talk = function(self, words) print(self.name.."说:"..words) end
以上代码在Person
表中加入一个键值对,键为talk
,值为一个函数。
好了,只要调用,Person.talk(Person, "你好")
,将会打印出:这我的很懒说:你好
。
不过在写程序时,你们都习惯把function
放在前面,这就是函数的语法糖:
function Person.talk(self, words) print(self.name.."说:"..words) end
这与上面的函数定义是等价的,可是这么写你就很难看出来talk
实际上是Person
表中的一个键,其对应的值为一个函数。
固然嘴巴都是长在本身身上的,说话只能本身说,不可能本身张嘴别人说话,因此每次都传个self参数实在是有点不美观,因而冒号语法糖上场。
咱们还能够这么定义人类的说话功能:
function Person:talk(words) print(self.name.."说:"..words) end
这与上面两段代码都是等价的,它的变化是少了self
的参数,将点Person.talk
改成了冒号Person:talk
。
可是函数体内,却依然可使用self
,在使用:
代替.
时,函数的参数列表的第一个参数再也不是words
,Lua会自动将self
作为第一个参数。这个self
参数表明的意思就是这个函数的实际调用者。
因此咱们调用Person:talk("你好")
与Person.talk(Person, "你好")
是等价的,这就是冒号语法糖带来的便利。
下面咱们须要理解在Lua的表中是怎么查找一个键所对应的值的。
假设咱们要在表p
中查找talk
这个键所对应的值,请看下面的流程图:
p中有没有talk这个键? 有 --> 返回talk对应的值 | 没有 | p中是否设置过metatable? 否 --> 返回nil | 有 | 在p的metatable中有没有__index这个键? 没有 --> 返回nil | 有 | 在p的metatable中的__index这个键对应的表中有没有talk这个键? 没有 --> 返回nil | 有,返回getmetatable(p).__index.talk
理解以上内容是本文的重点,反复阅读直至你记住了。
能够看到,因为metatable
和__index
这两个神奇的东西,Lua能在当前表中不存在这个键的时候找到其返回值。
下面将会讲一讲metatable
这个语言特性。
metatable的中文名叫作元表。它不是一个单独的类型,元表其实就是一个表。
咱们知道在Lua中表的操做是有限的,例如表不能直接相加,不能进行比较操做等等。
元表的做用就是增长和改变表的既定操做。只有设置过元表的表,才会受到元表的影响而改变自身的行为。
经过全局方法setmetatable(t, m)
,会将表t
的元表设置为表m
。经过另外一个全局方法getmetatable(t)
则会返回它的元表m
。
注意:全部的表均可以设置元表,然而新建立的空表若是不设置,是没有元表的。
元表做为一个表,能够拥有任意类型的键值对,其真正对被设置的表的影响是Lua规定的元方法键值对。
这些键值对就是Lua所规定的键,好比前面说到的__index
,__add
,__concat
等等。这些键名都是以双斜杠__
为前缀。其对应的值则为一个函数,被称为元方法(metamethod),这些元方法定义了你想对表自定义的操做。
例如:前面所说的__index
键,在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
pos
表中本没有z
这个键,经过设置pos
的元表为m
,并设置m
的__index
对应的方法,这样全部取不到的键都会返回“undefined”
了。
以上咱们了解到,元表的__index
属性其实是给表配备了找不到键时的行为。
注意:元表的__index
属性对应的也能够为一个表。
再举个栗子,但愿可以加深对元表和元方法的理解,__add
键,考虑如下代码:
--建立元表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
表自己是不能用+
连起来计算的,可是经过定义元表的__add
的方法,并setmetatable
到但愿有此操做的表上去,那些表便能进行加法操做了。
由于元表的__add
属性是给表定义了使用+号时的行为。
好,假设前面的内容你都没有疑问的阅读完毕话,咱们开始进入正题。
请先独立思考一会,咱们该怎么去实现一个Lua的类?
思考ing…
种种铺垫后,咱们的类是一个表,它定义了各类属性和方法。咱们的实例也是一个表,而后咱们类做为一个元表设置到实例上,并设置类的__index
值为自身。
例如人类:
--设置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("我是路人甲") --因而获得输出 --路人甲说:我是路人甲
为了方便,咱们给人类一个建立函数create
:
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 pa= Person.new() pa.name = "张三" pa:talk("路人甲") --张三说:我是路人甲
local pb= Person.new()
pb.name = "李四"
pb:talk("路人乙") --李四说:我是路人甲
我以为这样比metatable更加清晰明了,你以为呢。