Lua 學(xué)習(xí)筆記之二(進(jìn)階話題)

2023-03-11 13:56 更新

進(jìn)階話題

1.函數(shù)閉包

1.1 實(shí)例代碼

    function createCountdownTimer(second)
        local ms = second * 1000  --ms為countDown的Upvalue
        local function countDown()
            ms = ms -1
            return ms
        end

        return countDown
    end

    local timer1 = createCountdownTimer(1) 

    for i = 1, 3 do
        print(timer1()) 
    end

輸出結(jié)果:

999

998

997

1.2 關(guān)于函數(shù)閉包描述

  • Upvalue

    一個(gè)函數(shù)所使用的定義在它的函數(shù)體之外的局部變量(external local variable)稱為這個(gè)函數(shù)的upvalue。 在前面的代碼中,函數(shù)countDown使用的定義在函數(shù)createCountdownTimer 中的局部變量ms就是countDown的upvalue,但ms對(duì)createCountdownTimer而 言只是一個(gè)局部變量,不是upvalue。 Upvalue是Lua不同于C/C++的特有屬性,需要結(jié)合代碼仔細(xì)體會(huì)。

  • 函數(shù)閉包

一個(gè)函數(shù)和它所使用的所有upvalue構(gòu)成了一個(gè)函數(shù)閉包。 

  • Lua函數(shù)閉包與C函數(shù)的比較

Lua函數(shù)閉包使函數(shù)具有保持它自己的狀態(tài)的能力,從這個(gè)意義上說,可以 與帶靜態(tài)局部變量的C函數(shù)相類比。但二者有顯著的不同:對(duì)Lua來說,函數(shù) 是一種基本數(shù)據(jù)類型——代表一種(可執(zhí)行)對(duì)象,可以有自己的狀態(tài);但 是對(duì)帶靜態(tài)局部變量的C函數(shù)來說,它并不是C的一種數(shù)據(jù)類型,更不會(huì)產(chǎn)生 什么對(duì)象實(shí)例,它只是一個(gè)靜態(tài)地址的符號(hào)名稱。

2. 基于對(duì)象的實(shí)現(xiàn)方式

2.2  實(shí)例代碼

    local function create(name ,id )
        local data = {name = name ,id = id}  --data為obj.SetName,obj.GetName,obj.SetId,obj.GetId的Upvalue
        local obj = {}  --把需要隱藏的成員放在一張表里,把該表作為成員函數(shù)的upvalue。

        function obj.SetName(name)
            data.name = name 
        end

        function obj.GetName() 
            return data.name
        end

        function obj.SetId(id)
            data.id = id 
        end

        function obj.GetId() 
            return data.id
        end

        return obj
    end

輸出結(jié)果:

mycreate's id:1mycreate's name:Sam

mycreate's id:1mycreate's name:Lucy

2.2  有關(guān)對(duì)象實(shí)現(xiàn)的描述

實(shí)現(xiàn)方式:  把需要隱藏的成員放在一張表里,把該表作為成員函數(shù)的upvalue。

局限性:  基于對(duì)象的實(shí)現(xiàn)不涉及繼承及多態(tài)。但另一方面,腳本編程是否需要繼承和多態(tài)要視情況而定。 

3.元表

3.1  實(shí)例代碼(1):

    local t = {}
    local m = {a = "and",b = "Li Lei", c = "Han Meimei"}

    setmetatable(t,{__index = m})  --表{ __index=m }作為表t的元表

    for k,v in pairs(t) do  --窮舉表t
        print("有值嗎?")
        print(k,"=>",v)
    end

    print("-------------")
    print(t.b, t.a, t.c)

輸出結(jié)果:


Li LeiandHan Meimei

3.2  實(shí)例代碼(2):

    local function add(t1,t2)
        --‘#’運(yùn)算符取表長(zhǎng)度
        assert(#t1 == #t2)
        local length = #t1
        for i = 1,length do
            t1[i] = t1[i] + t2[i]
        end
        return t1
    end

    --setmetatable返回被設(shè)置的表
    t1 = setmetatable({1,2,3},{__add = add})
    t2 = setmetatable({10,20,30},{__add = add})

    for k,v in  pairs(t1) do
        print(k,"=>",v)
    end

    for k,v in  pairs(t2) do
        print(k,"=>",v)
    end

    print("---------兩元表相加--------------")
    t1 = t1 + t2
    for i = 1 ,#t1 do
        print(t1[i])
    end

輸出結(jié)果:

1=>1

2=>2

3=>3

1=>10

2=>20

3=>30

---------兩元表相加--------------

11

22

33

3.3  有關(guān)元表的描述:

定義 :

元表本身只是一個(gè)普通的表,通過特定的方法(比如setmetatable)設(shè)置到某個(gè)對(duì)象上,進(jìn)而影響這個(gè)對(duì)象的行為;一個(gè)對(duì)象有哪些行為受到元表影響以及這些行為按照何種方式受到影響是受Lua語言約束的。比如在前面的代碼里,兩個(gè)表對(duì)象的加法運(yùn)算,如果沒有元表的干預(yù),就是一種錯(cuò)誤;但是Lua規(guī)定了元表可以“重載”對(duì)象的加法運(yùn)算符,因此若把定義了加法運(yùn)算的元表設(shè)置到那兩個(gè)表上,它們就可以做加法了。元表是Lua最關(guān)鍵的概念之一,內(nèi)容也很豐富,請(qǐng)參考Lua文檔了解詳情。

元表與C++虛表的比較:

如果把表比作對(duì)象,元表就是可以改變對(duì)象行為的“元”對(duì)象。在某種程度上,元表可以與C++的虛表做一類比。但二者還是迥然不同的:元表可以動(dòng)態(tài)的改變,C++虛表是靜態(tài)不變的;元表可以影響表(以及其他類型的對(duì)象)的很多方面的行為,虛表主要是為了定位對(duì)象的虛方法(最多再帶上一點(diǎn)點(diǎn)RTTI)。 

4.  基于原型的繼承

4.1 實(shí)例代碼

    local Robot = { name = "Sam", id = 001 }
    function Robot:New(extension)
        local t = setmetatable(extension or { }, self) 
        self.__index = self
        return t
    end

    function Robot:SetName(name)
        self.name = name 
    end

    function Robot:GetName() 
        return self.name
    end

    function Robot:SetId(id)
        self.id = id end

    function Robot:GetId() 
        return self.id
    end

    robot = Robot:New()
    print("robot's name:", robot:GetName()) 
    print("robot's id:", robot:GetId()) 
    print("-----------------")

    local FootballRobot = Robot:New({position = "right back"})
    function FootballRobot:SetPosition(p) 
        self.position = p
    end

    function FootballRobot:GetPosition()
        return self.position 
    end

    fr = FootballRobot:New()
    print("fr's position:", fr:GetPosition()) 
    print("fr's name:", fr:GetName()) 
    print("fr's id:", fr:GetId()) 
    print("-----------------")

    fr:SetName("Bob")
    print("fr's name:", fr:GetName()) 
    print("robot's name:", robot:GetName())

輸出結(jié)果:

robot's name:Sam

robot's id:1


fr's position:right back

fr's name:Sam

fr's id:1


fr's name:Bob

robot's name:Sam

4.2 相關(guān)描述:

prototype模式一個(gè)對(duì)象既是一個(gè)普通的對(duì)象,同時(shí)也可以作為創(chuàng)建其他對(duì)象的原型的對(duì)象(即類對(duì)象,class object);動(dòng)態(tài)的改變?cè)蛯?duì)象的屬性就可以動(dòng)態(tài)的影響所有基于此原型的對(duì)象;另外,基于一個(gè)原型被創(chuàng)建出來的對(duì)象可以重載任何屬于這個(gè)原型對(duì)象的方法、屬性而不影響原型對(duì)象;同時(shí),基于原型被創(chuàng)建出來的對(duì)象還可以作為原型來創(chuàng)建其他對(duì)象。 

5.包

5.1 實(shí)例代碼:

hello.lua

    local pack = require "mypack"  --導(dǎo)入包[注:包的名字與定義包的文件的名字相同(除去文件名后綴,在前面的代碼中,就是“mypack”)]

    print(ver or "No ver defined!")
    print(pack.ver)

    pack.aFunInMyPack()

    print(aFunInMyPack or "No aFunInMyPack defined!")
    aFuncFromMyPack()

mypack.lua

module(..., package.seeall) --定義包
ver = "0.1 alpha"

function aFunInMyPack() 
    print("Hello!")
end

_G.aFuncFromMyPack = aFunInMyPack

輸出結(jié)果:

No ver defined!

0.1 alpha

Hello!

No aFunInMyPack defined!

Hello!

5.2有關(guān)包的描述:

  • 定義

包是一種組織代碼的方式。

  • 實(shí)現(xiàn)方式

一般在一個(gè)Lua文件內(nèi)以module函數(shù)開始定義一個(gè)包。module同時(shí)定義了一個(gè)新的包的函數(shù)環(huán)境,以使在此包中定義的全局變量都在這個(gè)環(huán)境中,而非使用包的函數(shù)的環(huán)境中。理解這一點(diǎn)非常關(guān)鍵。以前面的代碼為例, “module(..., package.seeall)”的意思是定義一個(gè)包,包的名字與定義包的文件的名字相同(除去文件名后綴,在前面的代碼中,就是“mypack”),并且在包的函數(shù)環(huán)境里可以訪問使用包的函數(shù)環(huán)境(比如,包的實(shí)現(xiàn)使用了print,這個(gè)變量沒有在包里定義,而是定義在使用包的外部環(huán)境中)。

  • 使用方式

一般用require函數(shù)來導(dǎo)入一個(gè)包,要導(dǎo)入的包必須被置于包路徑(packagepath)上。包路徑可以通過package.path或者環(huán)境變量來設(shè)定。一般來說,當(dāng)前工作路徑總是在包路徑中。

 現(xiàn)在官網(wǎng)已經(jīng)不提倡使用module了,官方給出了兩個(gè)理由(以?model(... , package ,seeall)?為例):

package.seeall 這種方式破壞了模塊的高內(nèi)聚,原本引入 "filename" 模塊只想調(diào)用它的 foobar() 函數(shù),但是它卻可以讀寫全局屬性,例如 "filename.os"。

module 函數(shù)壓棧操作引發(fā)的副作用,污染了全局環(huán)境變量。例如 module("filename") 會(huì)創(chuàng)建一個(gè) filename 的 table,并將這個(gè) table 注入全局環(huán)境變量中,這樣使得沒有引用它的文件也能調(diào)用 filename 模塊的方法。

  • 其他

請(qǐng)參考Lua手冊(cè)進(jìn)一步了解包的詳細(xì)說明。 

參考文獻(xiàn)《C/C++程序員的Lua快速入門》


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)