|
|
@@ -2,7 +2,560 @@
|
|
|
|
|
|
## 初识 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 源码
|
|
|
|
|
|
+
|