1. 程式人生 > >Lua菜鳥教程學習筆記三(重難點)

Lua菜鳥教程學習筆記三(重難點)

內容會持續更新,有錯誤的地方歡迎指正,謝謝!

上一篇文章:Lua菜鳥教程學習筆記二(一些細節)介紹了Lua的一些細節內容,但缺少重難點內容,本文總結一些重難點。

Lua 模組與包

定義模組

模組類似於一個封裝庫,從 Lua 5.1 開始,Lua 加入了標準的模組管理機制,可以把一些公用的程式碼放在一個檔案裡,以 API 介面的形式在其他地方呼叫,有利於程式碼的重用和降低程式碼耦合度。
Lua 的模組是由變數、函式等已知元素組成的 table,也就是說,模組的結構就是一個 table 的結構,因此可以像操作呼叫 table 裡的元素那樣來操作呼叫模組裡的常量或函式。
以下為建立自定義模組 module.lua,檔案程式碼格式如下:

-- 檔名為 module.lua
-- 定義一個名為 module 的模組
module = {}
 
-- 定義一個常量
module.temp= "這是一個常量"
 
-- 定義一個函式
function module.func1()
    io.write("這是一個公有函式!\n")
end
 
local function func2()
    print("這是一個私有函式!")
end
 
function module.func3() -- 公有函式
    func2()
end
 
return module

上面的 func2 宣告為程式塊的區域性變數,即表示一個私有函式,因此是不能從外部訪問模組裡的這個私有函式的,必須通過模組裡的公有函式來呼叫。

呼叫模組

Lua提供了一個名為require的函式用來載入模組。 執行 require 後會返回一個由模組常量或函式組成的 table,並且還會定義一個包含該 table 的全域性變數。

-- test_module.lua 檔案
-- module 模組為上文提到到 module.lua
require("module")
print(module.constant)
module.func3()
-- 以上程式碼執行結果為:
這是一個常量
這是一個私有函式!

或者給載入的模組定義一個別名變數,方便呼叫:

-- 別名變數 m
local m = require("module")

C 包

Lua和C是很容易結合的,使用C為Lua寫包。
與Lua中寫包不同,C包在使用前必須先載入並連線,在大多數系統中最容易的實現方式是通過動態連線庫機制。

local path = "/usr/local/lua/lib/libluasocket.so"
-- 或者 path = "C:\\windows\\luasocket.dll",這是 Window 平臺下
local f = assert(loadlib(path, "luaopen_socket")) -- 兩個引數分別是:庫的絕對路徑和初始化函式
f()  -- loadlib函式載入指定的庫並且連線到Lua,然而它並不開啟庫(也就是說沒有呼叫初始化函式),f()才真正開啟庫

Lua 元表(Metatable)

元表可以很好的簡化我們的程式碼功能,所以瞭解 Lua 的元表,可以讓我們寫出更加簡單優秀的 Lua 程式碼。

Lua table無法對兩個 table 進行操作,因此 Lua 提供了元表(Metatable),允許我們改變table的行為,每個行為關聯了對應的元方法。
有兩個很重要的函式來處理元表:

setmetatable(table,metatable): 對table設定元表(metatable),如果元表(metatable)中存在__metatable鍵值,setmetatable會失敗
getmetatable(table): 返回物件的元表(metatable)

__index 元方法:用來對錶訪問
(注意:__是兩個下劃線)__index 元方法總結:
Lua查詢一個表元素時的規則,其實就是如下3個步驟:

  1. 在表中查詢,如果找到,返回該元素,找不到則繼續
  2. 判斷該表是否有元表,如果沒有元表,返回nil,有元表則繼續。
  3. 判斷元表有沒有__index方法,如果__index方法為nil,則返回nil;如果__index方法是一個表,則重複1、2、3;如果__index方法是一個函式,則返回該函式的返回值。
mytable = setmetatable({key1 = "value1"}, {
  __index = function(mytable, key) -- 如果__index包含一個函式的話,Lua就會呼叫那個函式,table和鍵會作為引數傳遞給函式
    if key == "key2" then
      return "metatablevalue"
    else
      return nil
    end
  end
})

print(mytable.key1,mytable.key2)
-- 例項輸出結果為:
value1    metatablevalue
-- 我們可以將以上程式碼簡單寫成:
mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
print(mytable.key1,mytable.key2)

__newindex 元方法:用來對錶更新
__call 元方法:呼叫一個值時呼叫
__tostring 元方法:用於修改表的輸出行為

Lua 協同程式(coroutine)

Lua 協同程式(coroutine)與執行緒比較類似:擁有獨立的堆疊,獨立的區域性變數,獨立的指令指標,同時又與其它協同程式共享全域性變數和其它大部分東西。
在任一指定時刻只有一個協同程式在執行,並且這個正在執行的協同程式只有在明確的被要求掛起的時候才會被掛起。
在這裡插入圖片描述
舉例如下:

-- coroutine_test.lua 檔案
co = coroutine.create(
    function(i)
        print(i);
    end
)
-- 1為函式引數傳給co中的function的i
coroutine.resume(co, 1)   -- 1
print(coroutine.status(co))  -- dead
print("----------")

co = coroutine.wrap(
    function(i)
        print(i);
    end
)
co(1)
print("----------")

co2 = coroutine.create(
    function()
        for i=1,10 do
            print(i)
            if i == 3 then
                print(coroutine.status(co2))  --running
                print(coroutine.running()) --thread:XXXXXX
            end
            coroutine.yield()
        end
    end
)
-- co2中的function無引數,所以只需要傳co2
coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --3
print(coroutine.status(co2))   -- suspended
print(coroutine.running())
print("----------")
以上例項執行輸出結果為:

1
dead
----------
1
----------
1
2
3
running
thread: 0x7fb801c05868    false
suspended
thread: 0x7fb801c04c88    true
----------

從coroutine.running可以看出,coroutine在底層實現就是一個執行緒。當create一個coroutine時就是在新執行緒中註冊了一個事件。當使用resume觸發事件時,create的coroutine函式就被執行了,當遇到yield的時候就代表掛起當前執行緒,等候再次resume觸發事件。

resume和yield的配合強大之處在於,resume處於主程中,它將外部狀態(資料)傳入到協同程式內部;而yield則將內部的狀態(資料)返回到主程中。

function foo (a)
    print("foo 函式輸出", a)
    return coroutine.yield(2 * a) -- 返回  2*a 的值
end
 
co = coroutine.create(function (a , b)
    print("第一次協同程式執行輸出", a, b) -- co-body 1 10
    local r = foo(a + 1)
     
    print("第二次協同程式執行輸出", r)
    local r, s = coroutine.yield(a + b, a - b)  -- a,b的值為第一次呼叫協同程式時傳入
     
    print("第三次協同程式執行輸出", r, s)
    return b, "結束協同程式"                   -- b的值為第二次呼叫協同程式時傳入
end)
        
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("--分割線----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---分割線---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割線---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割線---")
-- 以上例項執行輸出結果為:

第一次協同程式執行輸出    1    10
foo 函式輸出    2
main    true    4
--分割線----
第二次協同程式執行輸出    r
main    true    11    -9
---分割線---
第三次協同程式執行輸出    x    y
main    true    10    結束協同程式
---分割線---
main    false    cannot resume dead coroutine
---分割線---

上述程式碼執行的流程如下:

  1. 呼叫resume,將協同程式喚醒,resume操作成功返回true,否則返回false;
  2. 協同程式執行;
  3. 執行到yield語句;
  4. yield掛起協同程式,第一次resume返回;
  5. 第二次resume,再次喚醒協同程式;(注意:此處resume的引數中,除了第一個引數,剩下的引數將作為yield的引數)
  6. yield返回;
  7. 協同程式繼續執行;
  8. 如果使用的協同程式繼續執行完成後繼續呼叫 resume方法則輸出:cannot resume dead coroutine