# Lua ## 初识 Lua ### 循环 与 迭代 #### 普通循环 ```lua for i = 0, 7, 2 do print(i) end ``` > 上述代码的输出是:0,2,4,6 普通循环 写法,定义 i 的初始值为 0,第二个 100 为 i 的终值,第三个 2 为每次循环的步长 #### 闭包函数迭代器 ```lua 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 之后,循环结束,闭包函数对象被释放 闭包函数迭代器支持状态传递 ```lua 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 ``` 输出内容是 ```bash 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值就停止 ```lua 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 ``` 输出内容如下 ```bash ipairs -------------- pairs -------------- 2 Tue 3 Wet Tue 2 Mon 1 Wet 3 ``` 注意,使用 ipairs 并没有执行循环体,因为 ipairs 是从 1 开始查找,按照 1、2、3 ... 进行索引遍历,由于 t 没有索引为 1 的 index,循环在一开始就结束了 ### 携程 ```cpp -- 创建协程 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 -- 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) ``` 测试代码输出 ```bash 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 ```lua for n in pairs(_G) do print(n) end print(_G == _G._G) ``` `_G` 本身也是一个 `table`,可以像操作 `table` 一样操作它 ```lua 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 表示调用当前函数的函数 ```lua 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)` 在指定环境中执行 字符串/文件 ```lua a = 1 -- 仍在默认全局环境里 do local _ENV = { _G = _G } -- 将当前块的环境改为仅含 _G 的表 _G.print(a) -- => nil(因为当前块环境里没有 a) _G.print(_G.a) -- => 1(从真正的全局表里读到 a) end ``` > 使用 _ENV 临时修改执行环境 ```lua 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 让代码在指定环境中运行 ### 面向对象 ```lua local St = {v = 10} function St.printf(self) print(self.v) end St.printf(St); ``` 类似 `printf` 这样每次调用都要把自己这个对象传入的写法,非常麻烦,而且一点也不 `OOP`,所以 lua 提供 **:** 来代替 . ```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 的方案,使用原型链的方式来实现继承 ```cpp 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` 的值 #### 继承 ```lua 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 ``` 用例 ```lua local sts1 = StSon:new() sts1:Add(100); sts1:Mul(2); sts1:printf() ``` ### Weak 表 由于 lua 有自己的垃圾回收机制,大部分情况下不用关心对象的 GC `Weak` 表的作用是告诉 GC 一个引用不应该阻止该对象被回收,它是个弱引用,可以被 GC 掉 如何定义一个 `table` 是 `weak table` 呢? - `metatable` 的 `__mode` 字符串包含小写的 `k` 表示这个 `table` 的 `keys` 都是 `weak` 的 - `metatable` 的 `__mode` 字符串包含小写的 `v` 表示这个 `table` 的 `values` 都是 `weak` 的 ```lua 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` `a` 的 `keys` 是 `weak` 的,`{ a = 1}` 没有对象引用,被 GC 掉了;`{ b = } `被 `key` 引用,因此没有被 GC ## Lua 源码