Lua笔记

5.6k 词

最近使用Lua过程中的一些笔记记录,零零散散的,主要有以下这么些内容:

版本兼容性处理

常接触到的Lua版本有5.1、5.2和5.3,在Lua中可以通过_VERSION获取版本:

1
2
3
4
5
6
7
if _VERSION == "Lua 5.3" then
print("5.3")
elseif _VERSION == "Lua 5.2" then
print("5.2")
elseif _VERSION == "Lua 5.1" then
print("5.1")
end

Lua5.1、5.2和5.3的一些常会遇到的API差异:

  • 5.1->5.2

    loadstring改为load

    setfenv/getfenv 改为 _ENV

  • 5.2->5.3

    unpack改为 table.unpack

一种较为简单的兼容API差异的方法是增加类似这样的代码:

1
2
load = load or loadstring
unpack = unpack or table.unpack

pack和unpack

在Lua中,table和逗号分隔的多个值的互相转化,即将多个值打包成table或者是把table解包成多个值。以下是几个具体的使用场景:

变长参数的函数

将多个值转为table,使用{...}

1
2
3
4
5
6
7
8
9
local function (...)
local args = {...}
print("got " .. #args .. " arguments")
for i,v in ipairs(args) do
print("arg" .. i .. " = " .. tostring(v))
end
end

func(1,2,3,{},"apple")

将会输出:

1
2
3
4
5
6
got 5 arguments
arg1 = 1
arg2 = 2
arg3 = 3
arg4 = table: 0x7f99e94044c0
arg5 = apple

另一种取值(遍历)的方法是使用select,当select的参数为"#"时,返回这一组参数的长度,当参数为整数时,表示截取此整数位置及其后的所有元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
local function (...)
local total = select("#",...)
print("got " .. total .. " arguments")
for i = 1, total do
print("arg" .. i .. " = " .. tostring(select(i,...)))
end

print("last arg " .. " = " .. tostring(select(-1,...)))

local argsFrom2 = {select(2,...)}
print(" --- nargs form the 2nd: ")
for i,v in ipairs(argsFrom2) do
print("arg" .. i .. " = " .. tostring(v))
end

end

func("heheda",1,2,3,{},"apple")

将会输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
got 6 arguments
arg1 = heheda
arg2 = 1
arg3 = 2
arg4 = 3
arg5 = table: 0x7f9483d04130
arg6 = apple
last arg = apple
---
args form the 2nd:
arg1 = 1
arg2 = 2
arg3 = 3
arg4 = table: 0x7f9483d04130
arg5 = apple

将table值赋给多个变量

1
2
3
4
5
6
local unpack = unpack or table.unpack
local t = {3,100,"good"}
local a, b, c = unpack(t)
print("a = ".. a)
print("b = ".. b)
print("c = ".. c)

将会输出:

1
2
3
a = 3
b = 100
c = good

处理有多个返回值的函数

只有放在最后的时候才能接收到所有的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
local function multiRet()
return "ret1","ret2"
end

local a,b,c
local prt = function()
print("a = " .. tostring(a) ..
" b = " .. tostring(b) ..
" c = " .. tostring(c))
end

a,b,c = multiRet()
prt()
a,b,c = multiRet(), "newValue"
prt()
a,b,c = "newValue", multiRet()
prt()

将会输出:

1
2
3
a = ret1   b = ret2   c = nil
a = ret1 b = newValue c = nil
a = newValue b = ret1 c = ret2

迭代器

在Lua中,习惯了用ipairspairs来做遍历,其实它们也只是比较特殊的函数而已:

1
function ipairs(t) end

传入参数是一个表,该函数返回三个值,依次是一个迭代函数f、表t0,因此可以使用:

1
2
3
for i,v in ipairs(t) do

end

这种格式来迭代(1,t[1]), (2,t[2]) ...这样成对的值,直到没有更多的整数索引。

1
function pairs(t) end

传入参数是一个表,该函数返回三个值,依次是函数next、表tnil,因此可以使用:

1
2
3
for k,v in pairs(t) do

end

这种格式来迭代表中所有的键值对。

函数next

1
function next(table, index) end

可用next遍历整个表,第一个参数是表,第二个参数是表内的一个索引。next会返回表的下一个索引和关联的值。当第二个参数是nil时,next会返回一个初始索引和其关联的值。当第二个参数是表的最后一个索引时,或者当表是空表而第二个参数是nil时,next会返回nil。如果第二个参数空缺,则认为是nil,此时你可以用next(t)来判断一个表是否为空表。表内索引值的迭代顺序是不确定的,即便是数字型索引。如果在使用next遍历过程中给遍历中的表增加新的值,将会出现为定义行为。但是你可以修改甚至清除当前已有的值。

使用next方法,则pairs函数可理解为:

1
2
3
function pair(t)
return next, t
end

raw方法

使用rawgetrawset避免触发__index__newindex方法:

1
2
3
4
5
6
7
8
9
10
11
12
local t = {a = "a"; b = "b"; c = "c"}
local mt = { __index = {d = "metatable-value d"}; __newindex = function(t,k,v) print("cant set value") end}
setmetatable(t,mt)

print(t["c"])
print(rawget(t,"c"))
print(t["d"])
print(rawget(t,"d"))
t["e"]= "e"
print(t["e"])
rawset(t,"f","raw-set f")
print(t["f"])

将会输出:

1
2
3
4
5
6
7
c
c
metatable-value d
nil
cant set value
nil
raw-set f

除此之外还有rawequal,避免触发__eq方法:

1
2
3
4
5
6
7
8
9
10
local eq = function(t1,t2)
return t1.name == t2.name
end
local t1 = setmetatable({name = "haha"},{__eq = eq})
local t2 = setmetatable({name = "haha"},{__eq = eq})

print("t1 == t2 -> " .. tostring(t1 == t2))
print("rawequal(t1,t2) -> " .. tostring(rawequal(t1,t2)))
print("t1 == t1 -> " .. tostring(t1 == t1))
print("rawequal(t1,t1) -> " .. tostring(rawequal(t1,t1)))

将会输出:

1
2
3
4
t1 == t2  -> true
rawequal(t1,t2) -> false
t1 == t1 -> true
rawequal(t1,t1) -> true

package相关

查找路径

require某个脚本被告知module not found或者不确定脚本路径从哪一级写起时,可以使用以下方法显示当前的查找路径。

1
print(package.path)

package.path中记录了所有的包含路径。如果要增加某个路径,只需像拼接字符串那样接在后边即可:

1
package.path = package.path .. ";/SomeFolder/?.lua"

模块重新加载

对于已经加载的Lua模块,如果再次require会直接得到第一次加载时的内容。加载过的模块都可以在
package.loadedpackage.preloaded 中找到。可以手动将其置为nil,然后再重新require。使用这种方法,可以在运行时不重启程序,修改Lua脚本并应用修改内容,用来调试Lua代码非常方便。

文件夹m下有文件mod1.lua 内容如下:

1
2
3
4
5
func = function()
print("old func")
end

print("mod1 is required")

测试代码,可以在命令行测试:

1
2
3
4
5
6
7
8
9
10
11
12
require ("m.mod1")
func()
print(tostring(package.loaded["m.mod1"]))
require ("m.mod1")

package.loaded["m.mod1"] = nil
print(tostring(package.loaded["m.mod1"]))

-- 此时修改mod1内容,将`old func`改为`new func`

require ("m.mod1")
func()

这种操作只会影响到下次require时执行的动作,但不会影响到已经加载到内存中的内容。

环境

默认情况下,全局变量都保存在一个名为_G的表里,也可以通过遍历_G获取全部的全局变量/函数,如:

1
2
3
4
5
6
7
8
t = {"a","b","c"}
print(tostring(t))
print(tostring(_G["t"]))
print(tostring(t[2]))
print(tostring(_G["t"][2]))
for k,v in pairs(_G) do
print( "_G[" ..tostring(k) .. "] = " .. tostring(v))
end

setfenv和getfenv

这个_G就是一个默认的函数环境,有时候我们可能会有一些需求要使用其它的环境而非默认的_G,在5.1版本的lua中我们可以使用setfenvgetfenv来设置和获取环境。

setfenv接收两个参数,第一个参数为一个函数或者一个数字,第二个参数为设置的目标环境表。当第一个参数为函数时,表示设置该函数的环境,若第一个参数为数字(1、2、3…),1表示当前函数,2表示更外一层即调用当前函数的函数,以此类推。

1
2
3
4
5
6
7
8
9
local newEnv = {}
local prt = print
newEnv.print = function(arg)
prt("[new env print] " .. arg)
end
setfenv(1,newEnv)


print("a")

_ENV

5.2及更高版本的lua废弃了setfenvgetfenv,取而代之使用_ENV来设置环境,如:

1
2
3
4
5
6
7
8
9
local newEnv = {}
local prt = print
newEnv.print = function(arg)
prt("[new env print] " .. arg)
end
_ENV = newEnv


print("a")

print是新的环境中的函数。

沙盒环境

制作沙盒环境,只能访问到希望访问到的函数,并且对全局变量的修改也都是在临时的新环境中进行,上边的例子就是一种应用,将希望在新环境中使用的函数(全局的print),使用upvalue的形式(prt)引用到新的环境中newEnv.print

如果在新环境中使用_G的函数,另一种方法是:

1
2
3