佳木斯湛栽影视文化发展公司

主頁(yè) > 知識(shí)庫(kù) > Lua中類的實(shí)現(xiàn)原理探討(Lua中實(shí)現(xiàn)類的方法)

Lua中類的實(shí)現(xiàn)原理探討(Lua中實(shí)現(xiàn)類的方法)

熱門標(biāo)簽:網(wǎng)站排名優(yōu)化 服務(wù)外包 Linux服務(wù)器 AI電銷 地方門戶網(wǎng)站 百度競(jìng)價(jià)排名 呼叫中心市場(chǎng)需求 鐵路電話系統(tǒng)

Lua中沒(méi)有類的概念,但我們可以利用Lua本身的語(yǔ)言特性來(lái)實(shí)現(xiàn)類。

下文將詳細(xì)的解釋在Lua中實(shí)現(xiàn)類的原理,涉及到的細(xì)節(jié)點(diǎn)將拆分出來(lái)講,相信對(duì)Lua中實(shí)現(xiàn)類的理解有困難的同學(xué)將會(huì)釋疑。

類是什么?

想要實(shí)現(xiàn)類,就要知道類到底是什么。

在我看來(lái),類,就是一個(gè)自己定義的變量類型。它約定了一些它的屬性和方法,是屬性和方法的一個(gè)集合。

所有的方法都需要一個(gè)名字,即使是匿名函數(shù)實(shí)際上也有個(gè)名字。這就形成了方法名和方法函數(shù)的鍵值映射關(guān)系,即方法名為鍵,映射的值為方法函數(shù)。

比如說(shuō)有一個(gè)類是人,人有一個(gè)說(shuō)話的方法,那就相當(dāng)于,人(Person)是一個(gè)類,說(shuō)話(talk)是它的一個(gè)方法名,說(shuō)話函數(shù)是它的實(shí)際說(shuō)話所執(zhí)行到的內(nèi)容。

人也有一個(gè)屬性,比如性別,性別就是一個(gè)鍵(sex),性別的實(shí)際值就是這個(gè)鍵所對(duì)應(yīng)的內(nèi)容。

理解了類實(shí)際上是一個(gè)鍵值對(duì)的集合,我們不難想到用Lua中自帶的表來(lái)實(shí)現(xiàn)類。

實(shí)例是什么?

如果理解了類實(shí)際就是一個(gè)鍵值映射的表,那么我們?cè)賮?lái)理解實(shí)例是什么。

實(shí)例就是具有類的屬性和方法的集合,也是一個(gè)表了。聽(tīng)起來(lái)好像和類差不多?

類全局只有一個(gè)集合,相當(dāng)于上帝,全局只有一塊內(nèi)存;而實(shí)例就普通了,普天之下有那么多人,你可以叫A說(shuō)一句話,A便執(zhí)行了他的說(shuō)話方法,但是不會(huì)影響B(tài)的說(shuō)話。因?yàn)樗麄兪菍?shí)例,彼此分配著不同的內(nèi)存。

說(shuō)了那么多廢話,其實(shí)實(shí)例就是由類創(chuàng)建出來(lái)的值,試著把類想象成類型而不是類。

兩個(gè)語(yǔ)法糖

試著創(chuàng)建一個(gè)人類 Person

復(fù)制代碼 代碼如下:

Person = {name="這個(gè)人很懶"}

以上代碼將Person初始化為一個(gè)表,這個(gè)表?yè)碛幸粋€(gè)為name的鍵,其默認(rèn)值是"這個(gè)人很懶"。

說(shuō)成白話就是人類擁有一個(gè)叫名字的屬性。

那就再賦予人類一個(gè)說(shuō)話的功能吧。

復(fù)制代碼 代碼如下:

Person.talk = function(self, words)
    print(self.name.."說(shuō):"..words)
end

以上代碼在Person表中加入一個(gè)鍵值對(duì),鍵為talk,值為一個(gè)函數(shù)。

好了,只要調(diào)用,Person.talk(Person, "你好"),將會(huì)打印出:這個(gè)人很懶說(shuō):你好。

不過(guò)在寫(xiě)程序時(shí),大家都習(xí)慣把function放在前面,這就是函數(shù)的語(yǔ)法糖:

復(fù)制代碼 代碼如下:

function Person.talk(self, words)
    print(self.name.."說(shuō):"..words)
end

這與上面的函數(shù)定義是等價(jià)的,但是這么寫(xiě)你就很難看出來(lái)talk其實(shí)是Person表中的一個(gè)鍵,其對(duì)應(yīng)的值為一個(gè)函數(shù)。

當(dāng)然嘴巴都是長(zhǎng)在自己身上的,說(shuō)話只能自己說(shuō),不可能自己張嘴別人說(shuō)話,所以每次都傳個(gè)self參數(shù)實(shí)在是有點(diǎn)不美觀,于是冒號(hào)語(yǔ)法糖上場(chǎng)。

我們還可以這么定義人類的說(shuō)話功能:

復(fù)制代碼 代碼如下:

function Person:talk(words)
    print(self.name.."說(shuō):"..words)
end

這與上面兩段代碼都是等價(jià)的,它的變化是少了self的參數(shù),將點(diǎn)Person.talk改為了冒號(hào)Person:talk。

但是函數(shù)體內(nèi),卻依然可以使用self,在使用:代替.時(shí),函數(shù)的參數(shù)列表的第一個(gè)參數(shù)不再是words,Lua會(huì)自動(dòng)將self做為第一個(gè)參數(shù)。這個(gè)self參數(shù)代表的意思就是這個(gè)函數(shù)的實(shí)際調(diào)用者。

所以我們調(diào)用Person:talk("你好")與Person.talk(Person, "你好")是等價(jià)的,這就是冒號(hào)語(yǔ)法糖帶來(lái)的便利。

如何查找表中的元素?

下面我們需要理解在Lua的表中是怎么查找一個(gè)鍵所對(duì)應(yīng)的值的。

假設(shè)我們要在表p中查找talk這個(gè)鍵所對(duì)應(yīng)的值,請(qǐng)看下面的流程圖:

復(fù)制代碼 代碼如下:

p中有沒(méi)有talk這個(gè)鍵? 有 --> 返回talk對(duì)應(yīng)的值
        |
       沒(méi)有
        |
p中是否設(shè)置過(guò)metatable? 否 -->  返回nil
        |
        有
        |
在p的metatable中有沒(méi)有__index這個(gè)鍵? 沒(méi)有 -->  返回nil
        |
        有
        |     
在p的metatable中的__index這個(gè)鍵對(duì)應(yīng)的表中有沒(méi)有talk這個(gè)鍵? 沒(méi)有 --> 返回nil
        |
        有,返回getmetatable(p).__index.talk

理解以上內(nèi)容是本文的重點(diǎn),反復(fù)閱讀直至你記住了。

可以看到,由于metatable和__index這兩個(gè)神奇的東西,Lua能在當(dāng)前表中不存在這個(gè)鍵的時(shí)候找到其返回值。

下面將會(huì)講一講metatable這個(gè)語(yǔ)言特性。

對(duì)metatable的理解

metatable是什么?

metatable的中文名叫做元表。它不是一個(gè)單獨(dú)的類型,元表其實(shí)就是一個(gè)表。

我們知道在Lua中表的操作是有限的,例如表不能直接相加,不能進(jìn)行比較操作等等。

元表的作用就是增加和改變表的既定操作。只有設(shè)置過(guò)元表的表,才會(huì)受到元表的影響而改變自身的行為。

通過(guò)全局方法setmetatable(t, m),會(huì)將表t的元表設(shè)置為表m。通過(guò)另一個(gè)全局方法getmetatable(t)則會(huì)返回它的元表m。

注意:所有的表都可以設(shè)置元表,然而新創(chuàng)建的空表如果不設(shè)置,是沒(méi)有元表的。

元方法

元表作為一個(gè)表,可以擁有任意類型的鍵值對(duì),其真正對(duì)被設(shè)置的表的影響是Lua規(guī)定的元方法鍵值對(duì)。

這些鍵值對(duì)就是Lua所規(guī)定的鍵,比如前面說(shuō)到的__index,__add,__concat等等。這些鍵名都是以雙斜杠__為前綴。其對(duì)應(yīng)的值則為一個(gè)函數(shù),被稱為元方法(metamethod),這些元方法定義了你想對(duì)表自定義的操作。

例如:前面所說(shuō)的__index鍵,在Lua中它所對(duì)應(yīng)的元方法執(zhí)行的時(shí)機(jī)是當(dāng)查找不存在于表中的鍵時(shí)應(yīng)該做的操作??紤]以下代碼:

復(fù)制代碼 代碼如下:

--定義元表m
m = {}
--定義元表的__index的元方法
--對(duì)任何找不到的鍵,都會(huì)返回"undefined"
m.__index = function ( table, key )
  return "undefined"
end  
 
--表pos
pos = {x=1, y=2}
--初始沒(méi)有元表,所以沒(méi)有定義找不到的行為
--因?yàn)閦不在pos中,所以直接返回nil
print(pos.z) -- nil
--將pos的元表設(shè)為m
setmetatable(pos, m)
--這是雖然pos里仍然找不到z,但是因?yàn)閜os有元表,
--而且元表有__index屬性,所以執(zhí)行其對(duì)應(yīng)的元方法,返回“undefined”
print(pos.z) -- undefined

pos表中本沒(méi)有z這個(gè)鍵,通過(guò)設(shè)置pos的元表為m,并設(shè)置m的__index對(duì)應(yīng)的方法,這樣所有取不到的鍵都會(huì)返回“undefined”了。

以上我們了解到,元表的__index屬性實(shí)際上是給表配備了找不到鍵時(shí)的行為。

注意:元表的__index屬性對(duì)應(yīng)的也可以為一個(gè)表。

再舉個(gè)栗子,希望能夠加深對(duì)元表和元方法的理解,__add鍵,考慮以下代碼:

復(fù)制代碼 代碼如下:

--創(chuàng)建元表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都設(shè)置為m
local table1 = setmetatable({10, 11, 12}, m)
local table2 = setmetatable({13, 14, 15}, m)
 
--表本來(lái)是不能執(zhí)行 + 操作的,但是通過(guò)元表,我們做到了!
for k, v in pairs(table1 + table2) do
  print(k, v)
end
--print
--1 23
--2 25
--3 27

表本身是不能用+連起來(lái)計(jì)算的,但是通過(guò)定義元表的__add的方法,并setmetatable到希望有此操作的表上去,那些表便能進(jìn)行加法操作了。

因?yàn)樵淼腳_add屬性是給表定義了使用+號(hào)時(shí)的行為。

類的實(shí)現(xiàn)手段

好,假設(shè)前面的內(nèi)容你都沒(méi)有疑問(wèn)的閱讀完畢話,我們開(kāi)始進(jìn)入正題。

請(qǐng)先獨(dú)立思考一會(huì),我們?cè)撛趺慈?shí)現(xiàn)一個(gè)Lua的類?

思考ing…

種種鋪墊后,我們的類是一個(gè)表,它定義了各種屬性和方法。我們的實(shí)例也是一個(gè)表,然后我們類作為一個(gè)元表設(shè)置到實(shí)例上,并設(shè)置類的__index值為自身。

例如人類:

復(fù)制代碼 代碼如下:

--設(shè)置Person的__index為自身
Person.__index = Person  
 
--p是一個(gè)實(shí)例
local p = {}
 
--p的元表設(shè)置為Person
setmetatable(p, Person)
 
p.name = "路人甲"
 
--p本來(lái)是一個(gè)空表,沒(méi)有talk這個(gè)鍵
--但是p有元表,并且元表的__index屬性為一個(gè)表Person
--而Person里面有talk這個(gè)鍵,于是便執(zhí)行了Person的talk函數(shù)
--默認(rèn)參數(shù)self是調(diào)用者p,p的name屬性為“路人甲”
p:talk("我是路人甲")
 
--于是得到輸出
--路人甲說(shuō):我是路人甲

為了方便,我們給人類一個(gè)創(chuàng)建函數(shù)create:

復(fù)制代碼 代碼如下:

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("我是路人甲") --路人甲說(shuō):我是路人甲
pb:talk("我是路人乙") --路人乙說(shuō):我是路人乙

這樣我們可以很方便用Person類創(chuàng)建出pa和pb兩個(gè)實(shí)例,這兩個(gè)實(shí)例都具備Person的屬性和方法。

以上便是Lua實(shí)現(xiàn)一個(gè)類的方法,至于類的繼承,當(dāng)成一次練習(xí)吧,請(qǐng)大家思考~

您可能感興趣的文章:
  • Lua實(shí)現(xiàn)類繼承
  • Lua中類的實(shí)現(xiàn)

標(biāo)簽:湘潭 黃山 湖南 衡水 銅川 蘭州 仙桃 崇左

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《Lua中類的實(shí)現(xiàn)原理探討(Lua中實(shí)現(xiàn)類的方法)》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266
    安远县| 望奎县| 黄冈市| 凭祥市| 天门市| 武汉市| 洪湖市| 钟祥市| 建始县| 奈曼旗| 巴中市| 平原县| 绥德县| 庄浪县| 宁安市| 连城县| 裕民县| 井冈山市| 锦屏县| 封开县| 罗江县| 昂仁县| 伊金霍洛旗| 海南省| 梁河县| 太仆寺旗| 门头沟区| 大姚县| 上虞市| 通海县| 田东县| 武强县| 昭苏县| 石狮市| 威远县| 双辽市| 泸定县| 大城县| 扶余县| 吉林市| 岳西县|