原文地址 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更加清晰明瞭,你以爲呢。