Lua 基礎

Lua

Lua 是一種由C編寫的解釋性腳本語言。編程

基礎

  1. 語言的基礎 變量、數據結構、流程控制、編程風格等。
  2. 語言的模型 模塊化方式、內存管理。

變量

  1. lua 全部變量都是弱類型限制的。
  2. local 修飾則爲局部變量,僅在當前塊中有效,事後自動釋放。
  3. local 修飾則爲全局變量,直接進入_G _ENV 等全局數組中。
  • 類型
類型 示例 備註
nil x=nil nil 就是null
boolean x=false nil,false=false,true,非nil=true
number x=1 y=1.2 number自適應於整型、浮點型
string x='str' 字符串類型,無字符類型
function x=function()end 函數亦是基本的變量類型
coroutine 協程類型
userdata C/C++ 數據類型
table x={} lua 核心數據結構
  • 基本方式
-------------------------------------
-- 兩種方式定義函數,效果同樣
local function def()
    -- 棧式返回值
    return true,'hello world'
end

local def = function()
    return true,'hello world'
end

-- 棧式賦值
local a,b = def()

-- 二元賦值
local a = 0 or 1     -- 此時 a 爲 0
local a = nil or 1   -- 此時 a 爲 1

-- 三元賦值
local a = true and 1 or 0   -- a 爲 1
local a = false and 1 or 0  -- a 爲 0

-- 最多見的方法
a = tostring(123456)
a = tonumber("123456")
a = type(a)
  • string
-- base定義
str = 'hello world'
-- 經常使用定義,內部可攜帶:單引號且不用轉義
str = "hello'-'world"
-- 多行字符串
str = [[
    hello world!
]]

-- 字符串長度
print(string.len("str"),#str)
-- 字符串鏈接
print('hello '..'world')

-- 大小寫轉化
string.upper(a)
string.lower(a)

-- char ==> string
string.char()
-- string ==> char
string.byte()

-- 截取字串
string.sub(str,[start],[end])
-- 查找字串位置
string.find(str,sub_str,[start],[end])

-- % 佔位符格式化字符串,%d數字 %s 字符串 %% %
string.format("hello %s %d","word",123)
  • array
-- 數組定義方式
local x = {1,2,3,'4'}

-- 數組類型:table,實際並不是如此
print(type(x))
-- 數組、字符串的下標都從 1 開始
print(#x, #'hello', x[1])

-- 數組新增
table.insert(x,1,10)
-- 數組刪除某項值
table.remove(x,1)

-- 鏈接大量字符串,消耗少,速度快
table.concat(x,"-")
  • 引用
  1. 標準引用都是強引用模式,通常對象亦可分爲引用對象,已經非引用對象。
~ 傳遞 複製
基礎類型 值傳遞 深複製
引用類型 引用傳遞 淺複製
local num = 100
local tmp = { a = 100, b = 120 }
-- 此時執行 值傳遞
dat = num, dat = tmp.a
-- 此時執行 引用傳遞
local test = tmp


-- 弱引用
local tmp = {}

-- key,弱引用
setmetatable(tmp, { _mode = "k" })
-- value,弱引用
setmetatable(tmp, { _mode = "v" })
-- key-value, 弱引用
setmetatable(tmp, { _mode = "kv" })

table

  • 說明
  1. lua數組、表並不同,須要注意。
  2. 迭代器,可迭代全部table類型。
  3. table數組以及表,內存是一致的,表須要遍歷,數組可直接定位。
~ pairs ipairs
迭代時遇到nil 跳過本次 中止迭代
x = {1,nil,3,4}

-- 1
for _,v in pairs(x) do
    print(v)
end
-- 1 3 4
for _,v in ipairs(x) do
    print(v)
end
  • 映射表
-- 基本定義方式
local p = { a=10, b=20 }

-- 索引命中方式
print(p["a"], p.a)
-- 新增索引項
p.c = 100

-- 字符串索引,任意合法的字符串均可以做爲鍵
p[" "] = 123456

-- 映射:將內存數值映爲可讀字段
local state = { login = 1, logoff = 0 }
  • 元表
  1. index 元表,本質是添加一個附表。
  2. newindex 元表,則是在表添加新索引時執行的操做。
-- index 元表
local tmp = { x=1, y=2 }
setmetatable( tmp, { __index = {
    z = 10
}})
-- 另外一種方式
local tmp = setmetatable({x=1,y=2},{__index={z=10}})

-- 索引爲nil或不在原始表中,則會在附表中查找
print(tmp.x, tmp.y)

-- newindex
local tmp = { x=1, y=2 }
setmetetable( tmp, { __newindex=function(tab,key,val)
                                    -- 此處可不執行,用以鎖定 table
                                    rawset(tab,key,'val:'..str(val))
                                end
                    })
-- 新增索引,執行newindex方法
-- y = 'val:100'
tmp.y = 100
-- 此時y已存在,不執行newindex
-- y = 100
tmp.y = 100

-- rawget 獲取表中 key(字符串) value
local dat = rawget(tab,"key")
-- 設置表 tab,key(字符串) val
rawset(tab,key,val)
  • function
  1. 匿名函數,異步回調。
  2. 閉包函數,須要注意可能形成內存泄漏。
-- 異步回調
local function demo(args, fun)
    print("function demo is running")
    fun(args)
end
-- 將匿名函數作爲參數傳遞至原方法內部
demo("hello world", function(args)
    print(args)
end)

-- 閉包函數
local function demo(args)
    -- 閉包函數返回後, args參數不會被釋放
    return function()
        print(args)
    end
end

demo("hello world")

控制

  • 運算符
1. 算術運算
+ - * / // % ^  -- / 除法運算 // 整除運算

2. 關係運算
== ~= > >= < <=

3. 邏輯運算
and or not

4. others
. [] () {} , : # .. =
  • 條件控制
-- 順序判斷
if true then
    print(true)
elseif true then
    print(true)
else
    print(false)
end

-- 經常使用判斷
if true then
    print(true)
end
  • 循環控制
--------------------------------------------
-- lua沒有 `switch` 選擇循環
-- lua沒有` continue`退出,僅存在 `break`退出塊。

-- while循環
while(true) do
    print(true)
    break
end
-- repeat循環,true爲退出條件
repeat
    print(false)
until false
-------------------------------------------
-- for 頭部默認爲內部塊變量,執行完畢自動釋放

-- start stop step
for i = 0,10,1 do
   print(i)
end
-- k爲索引,v爲值,同時適配與表格和數組
for k,v in pairs(tab) do
    print(k,'--',v)
end
--------------------------------------------
--  for 頭部亦可引用外部變量

local i
for i = 0,10,1 do
   print(i)
end
local k,v
for k,v in pairs(tab) do
    print(k,'--',v)
end
--------------------------------------------
-- _ 虛變量引用
for _,v in pairs(tab) do
    print(v)
end
  • 異常控制
-- 若此處爲false,程序中斷並拋異常
assert(1 == 1)

-- 以保護模式執行某方法,程序不會中斷
local ok,err = pcall(demo())

local function demo(x,y)
    print(x/y)
end

-- 以保護模式執行 demo,參數在其後,成功返回true,失敗返回 false,err
local ok, err = pcall(demo, 10,5)

print(ok,err)

local function print_err()
    print(debug.traceback())
end

-- 執行 demo,出錯執行 print_end,參數在其後
xpcall(demo,print_err(),0,10)

模塊

面向對象

lua 面向對象是一種僞對象,實際是一個衍生的索引表。數組

  • 模塊,類,對象
  • 模塊:塊私有變量或方法僅模塊內部生效,外部沒法使用
  • 類: class僅可讀寫全部 .級的方法以及變量。
  • 對象:obj僅可讀寫全部 self級的變量以及方法,僅可讀全部 . 級變量以及方法,強寫class級變量實際是新建一個obj級變量,對整個class並不生效。
  • 讀寫分離

模塊,類,對象之間的讀寫應該分離以避免出現系統性error,全部超出限度的強寫實際都是新建一個當前等級的變量,原始上一級變量並未發生改變。數據結構

模塊 對象
模塊 讀寫
讀寫
對象 讀寫
-- class
--------------------------------------------
local class = {}

-- 模塊私有方法,僅僅塊內生效
local function example()
    print("example")
end

-- 能夠看做類變量
class.size = 100

-- 能夠看做類方法
function class.print_size()
    print(class.size)
end

-- 類new對象的方法
function class:new(x,y)
    cla = {}
    -- 對象變量
    cla.x = x or 1
    cla.y = y or 2
    setmetatable(cla,{__index=self})
    return cla
end

-- 對象方法
function class:print()
    print(self.x,self.y)
end

return class

-------------------------------------------------
-- obj

-- 引用塊
local class = require("example")

-- class訪問
class.print_size()

-- obj 訪問
local obj = class:new(10,20)

-- obj -> class 訪問
obj.print_size()

面向切面

面向切面編程是一種特殊的設計方式,面向對象的主要目的是爲了代碼複用,面向切面則是一種函數結構化方法。閉包

-- 狀態碼
local STATE = {
    PREPARE = 0,
    FIGHT   = 1,
    REWARD  = 2,
    REFRESH = 3,
}
-- 主方法
local function running = {}
running[STATE.PREPARE] = function(comp)
    -- pass
end
running[STATE.FIGHT]   = function(comp)
    -- pass
end
running[STATE.REWARD]  = function(comp)
    -- pass
end
running[STATE.REFRESH] = function(comp)
    -- pass
end

-- running 主程序
while(true) do
    running[comp.state](comp)
end

高級特性

  • 加載模塊
  1. lua 全局變量存儲在:解釋器級變量_G _ENV中。
  2. lua package.loaded:存儲加載完成的模塊。
  3. lua package.path:require 適配路徑。
-- lua 模塊加載的基本路徑
local path = package.path

-- lua 檢測模塊是否已加載
if package.loaded["exp"] then
    print("exp 模塊已加載!")
end

-- lua 熱更新指定模塊
local function hotfix(file)
    if package.loaded[file] then
        package.loaded[file] = nil
    end
    require(file)
end
--------------------------------------------
local x = require("x")
-- 先掃描 require("x")路徑
-- 查找須要被加載的路徑是否已經被加載
-- 若模塊未被加載,則加載模塊,並返回索引
-- 若模塊已被加載,則直接返回索引

-- 加載某路徑下的文件,並執行
-- 執行語句則,加載編譯,執行
dofile("luafile.lua")
-- 加載指定文件,封裝爲一個函數返回
-- 索引僅需加載一次
local load = loadfile("luafile.lua")

--------------------------------------------
-- 弱類型,編譯解釋
-- 全部變量,在第一次被加載的時候,會被肯定下來
-- 加載時,會將代碼編譯爲機器碼,而後給解釋器
-- 之後執行時,皆使用加載時的配置類型
local y = 100   -- 編譯時肯定爲 int
local s = 'he'  -- 編譯時肯定爲 string
  • 預加載機制
  1. 預加載模塊時,變量與方法會進入內存。
  2. 預加載模塊時,語句塊僅會執行一次,並不會進入內存。
  3. 全部lua文件,在虛擬機啓動時即需完成預加載,除非後續熱更新。
local exp = require("example")
local ret = {}

-- 被預加載後,進入內存,但僅可局部使用
local inner = "inner value"
local function inner_fun()
    print("hello world\n")
end

-- 被預加載後,進入內存,外部亦可以使用
ret.outer = "outer value"
function ret.out_fun()
    print("hello world\n")
end

-- 預加載時
-- function 內的語句塊會進入內存
-- function 外的語句塊會執行一次,但不進入內存
print(inner)
inner_fun()

-- return 模塊
-- 語句塊會被執行一次,而後丟棄
-- 全部成員所有會被加載進入內存
-- 若成員被local修飾則非本模塊對象沒法訪問
return ret
  • 內存管理
  1. lua GC由解釋器隱士處理,亦可自行顯示處理。
  2. lua local變量會自動釋放,var=nil亦可釋放內存。
  3. _G _ENV 這兩個內部存儲全局變量。
  4. collectgarbage() GC管理。
  5. stop the world,標記清除。
local x = 100
local y = { a=100, b=200 }
z = 100
-- 不推薦使用全局變量
if _G["z"] then
    print("global var z"..z)
end
-- 釋放全局變量
y = nil
-- 釋放局部變量
x = nil
-- 釋放table內變量
y.a = nil

-- 執行一次 GC
collectbardage("collect")
-- 計算當前程序所佔總內存
local num = collectgarbage("count");

-- 慎用,解釋器中止自動GC,必須手動處理,不然內存會爆炸
collectbardage("stop")

經常使用

記錄一些經常使用的,易遺忘的方法以及小技巧。異步

經常使用模塊

  • 文件管理
local file = io.open(".../example.txt","w")

-- 讀寫外加參數便可
local str = file:read()
file:write("hellow world")

-- 關閉文件
io.close(file)
file:close()

lua 協程仍是單線程工做,只是同一時間裏只有一個協程在工做,且是守護線程。
擁有獨立的堆棧,獨立的局部變量,獨立的指令指針,同時又與其它協同程序共享全局變量和其它大部分東西。性能

-- 線程總得是無限循環的方法
local function done(dat)
    while()
        print(dat)
    end

    -- 協程執行至此處則 dead
end

-- 建立協程 suspended 掛起狀態
local cor = coroutine.create(done)

-- 獲取指定協程的現行狀態 dead,suspended,running
print(coroutine.status(cor))

-- 重啓或開啓協程,協程引用,外加參數(沒有能夠不加) running
coroutine.resume(cor,"hellow world")

-- 掛起當前正在運行的協程,並使得協程返回數據 dat,協程最多隻有一個能夠運行
coroutine.yield(dat)

-- 返回某個正在running 的協程的線程ID
coroutine.running()

生產者消費者模型測試

1.coroutine.resume(cor) :開啓協程等待返回值 status,valueui

2.coroutine.yield(dat) :掛起運行協程返回 dat

-- 弱類型語言的好處
local produce

-- 暫停程序 1 秒,獲取信息
local function step()
    os.execute("sleep 1")
    print(coroutine.running())
    print(coroutine.status(produce))
end

-- 生產者模型
local function productor()
    local i = 0
    while true do
         step()
         i = i + 1
         -- 掛起當前協程,並返回 i
         coroutine.yield(i)
    end
end

-- 消費者模型
local function consumer()
    while true do
        local status,value = coroutine.resume(produce)
        step()
        print(status,"--",value)
    end
end

produce = coroutine.create(productor)

consumer()

經常使用方法

不須要解釋,容易忘記的東西。

-- 執行系統cmd
os.execute("sleep 1")

-- 輸出 格式化日期 2020-01-01 01:02:56
print(os.date("%Y-%m-%d %H:%M:%S"))

-- 輸出當前時間戳
print(os.time())

-- 輸出程序已經執行的時間
print(os.clock())

-- 將錶轉化爲字符串
local function tostr(tab)
    local str =  "{ "
    str = str.."\n"
    for k, v in pairs(tab) do
       if type(v) ~= "table" then
           str = str.."[\""..k.."\"]"
           str = str..":"
           str = str..v
           str = str..","
           str = str.."\n"
       else
           str = str.."[\""..k.."\"]"
           str = str..":"
           str = str..tostr(v)
           str = str..","
           str = str.."\n"
       end
    end
    str = string.sub(str, 1, -3)
    str = str.."\n"
    str = str .." }"
    return str
end

經常使用設計

一些通用性的程序設計。

  • 平滑
-- 模塊儘可能使用,if return
if true then
    return "err"
end

-- 方法不變,選擇不一樣的參數執行
local function execute(param)
    print(param)
end

-- 參數不變,選擇不一樣的方法執行
local Execute = {}
Execute[CODE] = function(param)
    print(param)
end
  • 容錯
 
  • 冪等
-- 程序設計時,根據實際的

進階

  • 性能測試
-- 執行所用CPU時長
local begin     = os.clock()
-- 獲取CPU時間戳
local beginTime = os.time()
print("hello world")
local over     = os.clock()
local overTime = os.time()

-- 0.001212  0.001234  number
print(begin, over, type(over))
-- 1596786393  1596786393  number
print(beginTime, overTime, type(overTime))
  • 遍歷加速
  1. iparis 僅用於迭代數組類型table,遇到nil會退出。
  2. pairs 則可用於全部類型table,遇到nil則會跳過,當迭表明時是亂序的。
local tab = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }

-- 0.038 ms, 此處 #tab 會一直執行,故必然拖慢速度
for i = 1, #tab do
    print(tab[i])
end

-- 0.030 ms
local len = #tab
for i = 1, len do
    print(tab[i])
end
-- 0.035 ms
for _,val in pairs(tab) do
    print(val)
end
-- 0.043 ms
for _,val in ipairs(tab) do
    print(val)
end
-- 遍歷基礎數組,速度並沒有太多差別

local tab = {
    a = 1, b = 2, c = 3, d = 4, e = 5,
    f = 6, g = 7, h = 8, i = 9, j = 10
}

-- paris 迭代 數組、表格
-- pairs 迭表明格時,順序爲隨機序列
for k, v pairs(tab) do
    print(k, v)
end
  • 傳值與引用
  1. 全部的基礎類型對象都執行深複製。
  2. 全部的引用類型對象都執行淺複製。
相關文章
相關標籤/搜索