Ver código fonte

feat: 添加 lua 基本入门知识

Nicetry12138 3 meses atrás
pai
commit
9af129b7c9
1 arquivos alterados com 553 adições e 0 exclusões
  1. 553 0
      README.md

+ 553 - 0
README.md

@@ -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 源码
 
+