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,进而判断是执行循环体 还是 跳出循环
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 允许改变 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)
为了简化操作,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 版本之后被 移除
这里提供了两种替代方案
_ENV 重新绑定当前代码块环境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()
由于 lua 有自己的垃圾回收机制,大部分情况下不用关心对象的 GC
Weak 表的作用是告诉 GC 一个引用不应该阻止该对象被回收,它是个弱引用,可以被 GC 掉
如何定义一个 table 是 weak table 呢?
metatable 的 __mode 字符串包含小写的 k 表示这个 table 的 keys 都是 weak 的metatable 的 __mode 字符串包含小写的 v 表示这个 table 的 values 都是 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 a 的 keys 是 weak 的,{ a = 1} 没有对象引用,被 GC 掉了;{ b = }被 key 引用,因此没有被 GC