README.md 13 KB

Lua

初识 Lua

循环 与 迭代

普通循环

for i = 0, 7, 2 do
    print(i)
end

上述代码的输出是:0,2,4,6

普通循环 写法,定义 i 的初始值为 0,第二个 100 为 i 的终值,第三个 2 为每次循环的步长

闭包函数迭代器

function list_iter (t)
    local i = 0
    local n = table.getn(t)
    return function ()  
        i = i + 1  
        if i <= n then 
            return t[i] 
        end
    end 
end

t = {10, 20, 30} 
for element in list_iter(t) do 
    print(element) 
end 

关于上面的代码,虽然 list_iter 返回的是函数,但是对于这种情况来说,lua 的执行逻辑是:每次循环时都执行一次函数,直到函数的返回值为 nil 为止

由于闭包的存在,list_iter` 函数中的局部变量 i 并不会被释放,因为仍有函数对象持有该对象,知道函数对象被释放的时候才会释放,因此闭包每次执行 i 都会加一,直到遍历完整个 table 之后,循环结束,闭包函数对象被释放

闭包函数迭代器支持状态传递

local _var = 10;
local _state = 2;

function list_iter (t) 
  local i = 0 
  local n = #t 
  return function (state, var) 
    print("state = " .. state .. " var = " .. var)
    i = i + 1 
    if i <= n then return t[i] end
  end, _state, _var
end

local t = {4, 5, 6}

for index in list_iter(t) do
  print(index)
end

输出内容是

state = 2 var = 10
4
state = 2 var = 4
5
state = 2 var = 5
6
state = 2 var = 6

执行逻辑大概是 iter, index, state = list_iter(t),得到 index = 10, state = 2

每次循环时执行 index = iter(state , index) 之后判断 index 是否是 nil,进而判断是执行循环体 还是 跳出循环

lua 提供的 pairs 和 ipairs

ipairs函数专门用于遍历数组(即键是从1开始的连续整数的表)。它会按照1,2,3...的顺序遍历表,直到遇到nil值就停止

local t = {
  [2] = "Tue", 
  [3] = "Wet", 
  ["Mon"] = 1, 
  ["Tue"] = 2, 
  ["Wet"] = 3
}

print("ipairs -------------- ")

for k, v in ipairs(t) do
  print(k .. " " .. v)
end

print("pairs -------------- ")

for k, v in pairs(t) do
  print(k .. " " .. v)
end

输出内容如下

ipairs -------------- 
pairs -------------- 
2 Tue
3 Wet
Tue 2
Mon 1
Wet 3

注意,使用 ipairs 并没有执行循环体,因为 ipairs 是从 1 开始查找,按照 1、2、3 ... 进行索引遍历,由于 t 没有索引为 1 的 index,循环在一开始就结束了

携程

-- 创建协程
co = coroutine.create(function() 
    -- 协程函数体
    print("协程开始")
    coroutine.yield("暂停点") -- 挂起协程
    print("协程恢复")
    return "完成"
end)

-- 恢复执行
-- 第一次恢复 status = true value = 暂停点 
status, value = coroutine.resume(co) 
print(((status and "TRUE") or "FALSE") .. " " .. value)
-- 第二次恢复 status = true value = 完成 
status, value = coroutine.resume(co) 
print(((status and "TRUE") or "FALSE") .. " " .. value)
-- 第三次恢复 status = false value =  cannot resume dead coroutine 
status, value = coroutine.resume(co) 
print(((status and "TRUE") or "FALSE") .. " " .. value)

-- 其他API
-- coroutine.status(co) -- 获取协程状态:running, suspended, dead, normal
-- coroutine.wrap() -- 创建协程并返回函数包装器
-- coroutine.yield() -- 挂起当前正在执行的协程

Metatables 和 Metamethods

Metatables 允许改变 table 的行为,比如两个 table 相加,默认情况下是错误的,但是通过 setmetatble 设置 metatable 并实现 __add 函数即可实现两个 table 的相加操作

lua 提供以下几种 metamethods

  • 访问控制:

    • __index(t, k)t[k] 且原键为 nil 时调用
    • __newindex(t, k, v)t[k] = v 且原键为 nil 时调用
    • __metatable(保护)
    • __mode(弱值示例)
  • 可调用与字符串:

    • __call(构造/克隆)
    • __tostring(t) 字符串上下文时触发
  • 序列与拼接:

    • __len(a)#t 时触发
    • __concat(a, b) 通常 a .. b 时触发
  • 算术:

    • __unm(a) 返回同类型结果,对应 -a
    • __add(a, b)(加)
    • __sub(a, b)(减)
    • __mul(a, b)(乘)
    • __div(a, b)(除)
    • __idiv(a, b)
    • __mod(a, b)(余)
    • __pow(a, b)(幂)
  • 位运算(Lua 5.3+):

    • __band
    • __bor
    • __bxor
    • __bnot
    • __shl
    • __shr
  • 比较:

    • __eq(a, b)(相等)
    • __lt(a, b)(小于)
    • __le(a, b)(小于等于)
  • 迭代器(Lua 5.3 支持):

    • __pairs
    • __ipairs
-- Lua

-- 基础结构
local ST = {}
ST.v = 10

-- 元表
local MT = {}

-- 辅助:类型判定与取值/构造
local function isST(x)
  return type(x) == "table" and getmetatable(x) == MT
end

local function val(x)
  local tx = type(x)
  if tx == "number" then return x end
  if tx == "table" and isST(x) then
    return rawget(x, "v")
  end
  error("非法操作数:" .. tostring(x))
end

local function new(v)
  return setmetatable({ v = v }, MT)
end

-- 访问控制
function MT.__index(self, k)
  if k == "val" then                -- 别名:读取 self.val 等同 self.v
    return rawget(self, "v")
  end
  return rawget(self, k)            -- 其他键按原值(nil 时返回 nil)
end

function MT.__newindex(self, k, v)
  if k == "v" then                  -- 仅允许写 v
    rawset(self, "v", v)
  else
    error(("只读字段:%s"):format(tostring(k)))
  end
end

-- 可调用:克隆或重置 v,返回新实例
function MT.__call(self, nv)
  if nv == nil then
    nv = rawget(self, "v")
  end
  return new(nv)
end

-- tostring 与拼接
function MT.__tostring(self)
  return ("ST(v=%s)"):format(tostring(rawget(self, "v")))
end

function MT.__concat(a, b)
  local function S(x)
    return isST(x) and tostring(x) or tostring(x)
  end
  return S(a) .. S(b)
end

-- 长度:返回 v(整数化由调用方决定)
function MT.__len(self)
  return rawget(self, "v")
end

-- 算术
function MT.__unm(a)      return new(-val(a)) end
function MT.__add(a, b)   return new(val(a) + val(b)) end
function MT.__sub(a, b)   return new(val(a) - val(b)) end
function MT.__mul(a, b)   return new(val(a) * val(b)) end
function MT.__div(a, b)   return new(val(a) / val(b)) end
function MT.__idiv(a, b)  return new(val(a) // val(b)) end
function MT.__mod(a, b)   return new(val(a) %  val(b)) end
function MT.__pow(a, b)   return new(val(a) ^  val(b)) end

-- 按位(Lua 5.3+)
function MT.__band(a, b)  return new(val(a) & val(b)) end
function MT.__bor(a, b)   return new(val(a) | val(b)) end
function MT.__bxor(a, b)  return new(val(a) ~ val(b)) end
function MT.__bnot(a)     return new(~val(a)) end
function MT.__shl(a, b)   return new(val(a) << val(b)) end
function MT.__shr(a, b)   return new(val(a) >> val(b)) end

-- 比较
function MT.__eq(a, b) return val(a) == val(b) end
function MT.__lt(a, b) return val(a) <  val(b) end
function MT.__le(a, b) return val(a) <= val(b) end

-- 迭代(Lua 5.2/5.3 支持)
function MT.__pairs(self)
  return next, self, nil
end

function MT.__ipairs(self)
  local function iter(_, i)
    i = i + 1
    local v = rawget(self, "v") or 0
    if i <= v then return i, i end
  end
  return iter, self, 0
end

-- 资源关闭(Lua 5.4)
function MT.__close(self, err)
  rawset(self, "_closed", true)
end

-- 保护与命名
-- MT.__metatable = "locked"
MT.__name = "ST"
-- MT.__mode = "v"  -- 可选:弱值

-- 绑定元表到 ST(让 ST 自身成为实例)
setmetatable(ST, MT)

测试代码输出

local st1 = new(10);
local st2 = new(20);

print(getmetatable(st1))
print(getmetatable(st2))

print(st1 + st2)
print(st1 - st2)
print(st1 * st2)
print(st1 / st2)
print(st1 & st2)

环境

全局变量 environment

为了简化操作,Lua 将环境本身存储在一个全局变量 _G

_G._G 等价于 _G

for n in pairs(_G) do 
  print(n) 
end

print(_G == _G._G)

_G 本身也是一个 table,可以像操作 table 一样操作它

local declaredNames = {} 
function declare (name, initval)  
    rawset(_G, name, initval)  
    declaredNames[name] = true
end 

setmetatable(_G, {  
    __newindex = function (t, n, v) 
        if not declaredNames[n] then 
            error("attempt to write to undeclared var. "..n, 2) 
        else  
            rawset(t, n, v) -- do the actual set 
        end 
    end,  
    __index = function (_, n) 
        if not declaredNames[n] then 
            error("attempt to read undeclared var. "..n, 2) 
        else  
            return nil
        end
    end, 
}) 

像上面这样,给 _G 设置 metatable,当想要获取空对象是提示错误

rawset 可以不触发 metamethod 来给 table 设置值

非全局的环境

全局环境的问题就是一个小小的修改,可能会影响所有的程序

Lua 5.0 之后提供 setfenv 函数来改变一个函数的环境

setfenv 接受函数和新的环境作为参数,还接受一个数字表示栈顶的活动函数,比如 1 表示当前函数,2 表示调用当前函数的函数

a = 1 
setfenv(1, {_G = _G})  -- 此时修改环境 全局变量中有 a 但是局部变量中没有 a 了
_G.print(a) --> nil 环境修改,当前函数中没有局部变量 a
_G.print(_G.a) --> 1 

不过 setfenv 和 getfenv 在 Lua 5.2 版本之后被 移除

这里提供了两种替代方案

  1. 使用局部 _ENV 重新绑定当前代码块环境
  2. 使用 load(chunk, chunkname, mode, env) 在指定环境中执行 字符串/文件
a = 1 -- 仍在默认全局环境里 

do 
  local _ENV = { _G = _G } -- 将当前块的环境改为仅含 _G 的表 
  _G.print(a) -- => nil(因为当前块环境里没有 a) 
  _G.print(_G.a) -- => 1(从真正的全局表里读到 a) 
end

使用 _ENV 临时修改执行环境

a = 1 

local env = { _G = _G } -- 自定义环境(不含 a) 
local chunk = [[ 
  _G.print(a) -- => nil 
  _G.print(_G.a) -- => 1 
]] 

local f = load(chunk, "demo", "t", env) 

f()

使用 load 让代码在指定环境中运行

面向对象

local St = {v = 10}
function St.printf(self)
    print(self.v)
end
St.printf(St);

类似 printf 这样每次调用都要把自己这个对象传入的写法,非常麻烦,而且一点也不 OOP,所以 lua 提供 : 来代替 .

local St = {
    v = 10,
    printf2 = function(self) 
        print(self.v);
      end
}

function St:printf()
  print(self.v);
end

St:printf();
St.printf2(St);

使用 : 来 定义 函数 和 调用 函数

参考 JS 中实现 class 的方案,使用原型链的方式来实现继承

local St = { 
    v = 10
}

function St:printf()
  print(self.v);
end

function St:Add(val)
  self.v = self.v + val;
end

function St:new (o) 
  o = o or {} -- create object if user does not provide one 
  setmetatable(o, self) 
  self.__index = self 
  return o 
end

local s1 = St:new()

s1:Add(100);

print(s1.v);
print(St.v);
  • 由于 s1 没有名为 Add 的函数,会通过 metatable 查找,进而使用 St.Add 函数
  • 在调用 Add 函数时,由于 s1 没有 v 属性,会通过 metatable 找到 St.v,并通过 self. v = self.v + val 语句给 s1 添加 v 属性,并赋值

由于赋值时是给 s1 赋值,所以并没有修改 St.v 的值

继承

local StSon = St:new();

function StSon:Mul(val)
  self.v = self.v * val;
end

function StSon:new(o)
  o = o or {}
  setmetatable(o, StSon)
  self.__index = self
  return o
end

用例

local sts1 = StSon:new()

sts1:Add(100);
sts1:Mul(2);

sts1:printf()

Weak 表

由于 lua 有自己的垃圾回收机制,大部分情况下不用关心对象的 GC

Weak 表的作用是告诉 GC 一个引用不应该阻止该对象被回收,它是个弱引用,可以被 GC 掉

如何定义一个 tableweak table 呢?

  • metatable__mode 字符串包含小写的 k 表示这个 tablekeys 都是 weak
  • metatable__mode 字符串包含小写的 v 表示这个 tablevalues 都是 weak
a = {} 
b = {} 
setmetatable(a, b) 

b.__mode = "k" -- 现在 table a 的 keys 都是 weak 的 

key = { a = 1} -- 创建 key
a[key] = 1 
key = { b = 2} -- 重新赋值 key
a[key] = 2 

collectgarbage() -- 强制垃圾回收

for k, v in pairs(a) do 
  print(v) 
end --> 2

上述代码,此时只会输出 2 ,不会输出 1 和 2

这是因为 table akeysweak 的,{ a = 1} 没有对象引用,被 GC 掉了;{ b = }key 引用,因此没有被 GC

Lua 源码