类型

1
2
3
4
5
6
7
8
9
10
11
12
a = nil
print(type(a)) -- nil
a = 1
print(type(a)) -- number
a = "hah"
print(type(a)) -- string
a = true
print(type(a)) -- boolean
a = {}
print(type(a)) -- table
a = function () return 0 end
print(type(a)) -- function

运算符

逻辑运算

and or not

false和nil是false, 其它是true(0也是true)

如果x为false或nil则给x赋初值v

1
x = x or v

三元运算符a?b:c
在lua里是

1
(a and b) or c

连接运算符

1
2
3
4
.. -- 两个点
print("Hello " .. "World") --> Hello World
print(0 .. 1) --> 01

语句

  • if 语句
1
2
3
4
5
6
7
8
9
10
11
12
function show(a)
if a > 10 then
print("a > 10")
elseif a > 0 then
print("a = " .. a)
else
print("a < 0")
end
end
show(15) -- a > 10
show(5) -- a = 5
show(-5) -- a < 0
  • while 语句
1
2
3
4
5
6
7
8
function show(a)
while a > 10 do
a = a / 2
end
print(a)
end
show(15) -- 7.5
show(5) -- 5
  • repeat until 语句
1
2
3
4
5
6
7
function show(a)
repeat a = a * 2 until a > 100
print(a)
end
show(15) -- 120
show(5) -- 160
show(101) -- 202
  • for 语句(若以下三个参数被写成了表达式的形式,表达式只会被调用一次,且调用时间在循环开始前)
    • 初值
    • 终止值
    • 步长(默认为1)
1
2
3
4
5
6
for i = 1, 8, 3 do
print("i = " .. i)
end
-- i = 1
-- i = 4
-- i = 7
  • 泛型for
    1. 初始化,计算in后面表达式的值,表达式应该返回泛型for需要的三个值:迭代函数、状态常量、控制变量;如果返回值个数不够,用nil补足,多出被忽略
    2. 将状态常量和控制变量作为参数调用迭代函数
    3. 将迭代函数返回值赋给变量列表
    4. 如果返回的第一个值为nil,循环结束,否则执行循环体
    5. 回到2再次调用迭代函数
1
2
3
4
5
6
7
8
9
a = {1, 2, 3, hello = "lua", world = "world"}
for i, v in pairs(a) do
print(i .. " " .. v)
end
-- 1 1
-- 2 2
-- 3 3
-- hello lua
-- world world
  • ipairs(t):遇到nil则退出,只能对顺序表访问
1
2
3
4
5
6
table={[2]=3, hello="hello"} --> key为1的时候value为nil 所以退出了
for i,v in ipairs(table) do
print(i, v)
end
--> 没有输出
  • pairs(t):可以遍历表中所有key,可以对dict访问
1
2
3
4
5
6
table={hello="hello"}
for i,v in pairs(table) do
print(i, v)
end
--> hello hello

函数

  • 可返回多个结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
s, e = string.find("Hello Lua", "Lua") -- 返回开始和结束的下标
print(s, e)
function maximum(a)
local mi = 1 -- maximum index
local m = a[mi] -- maximum value
for i, val in ipairs(a) do
if val > m then
mi = i
m = val
end
end
return m, mi
end
print(maximum({1, 2, 3, 4, 5})) --> 5 5
  • 可变参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
f = string.find
a = {"Hello Lua", "Lua"}
print(f(table.unpack(a))) -- 5.2之后unpack被移到了table下
printResult = ""
function link(...)
local arg = { ... } -- 5.1之后需要手动把...变成arg了
for i, v in ipairs(arg) do
printResult = printResult .. tostring(v) .. "\t"
end
printResult = printResult .. "\n"
end
link("a", "b")
print(printResult)

闭包

当一个函数内部嵌套另一个函数定义时,内部函数体可以访问外部函数体的局部变量,这种特征称为词法定界。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
names = {"a", "b", "c"}
grades = {c = 10, b = 7, a = 8}
function sortbygrade (names, grades)
table.sort(names, function (n1, n2)
return grades[n1] > grades[n2]
end)
end
sortbygrade (names, grades)
for i, v in ipairs(names) do
print(v)
end
--> c a b

闭包是一个函数加上它可以正确访问的upvalues。闭包指值而不是函数,函数仅仅是闭包的一个原型声明。

1
2
3
4
5
6
7
8
9
10
11
function newCounter()
local i = 0 --> upvalue
return function()
i = i + 1 --> 虽然已经超出了local i的作用范围 但由于闭包的思想 成功访问了
return i
end
end
c1 = newCounter()
print(c1()) --> 1
print(c1()) --> 2

  • 闭包的应用

    1. 作为高阶函数的参数(table.sort的参数)
    2. 创建其他函数的函数(函数返回一个闭包)
    3. 回调函数(界面上按钮的回调)
    4. 创建一个安全的运行环境(比如要限制一个程序访问文件,可以用闭包来重定义io.open)
    5. 实现迭代器
  • 闭包的实现原理

Lua编译一个函数(编译期概念)时,会为他生成一个原型(prototype),其中包含函数对应的虚拟机指令、函数用到的常量和一些调试信息。
运行时,每当Lua执行一个形如function...end的表达式时,会创建一个新的数据对象,其中包含相应函数原型的引用及一个由所有upvalue引用组成的数组。这个数据对象称为闭包(运行时概念)。上述数组中的每个元素都是一个对upvalue的引用,可以通过该数据来访问外部局部变量。在Lua5.2之前,闭包中还包括一个对环境(environment)的引用,环境实质就是一个table,函数可以在该表中索引全局变量,从Lua5.2开始取消了闭包中的环境,引入_ENV来设置闭包环境。

upvalue初始存在栈上,由指针指向,当运行时,离开变量作用域时,会复制到upvalue的结构中去,改变指针的位置到upvalue结构中对应位置。Lua维护一条链表,该链表中每个节点对应一个打开的upvalue结构,打开的upvalue是指当前正指向栈局部变量的upvalue。当Lua创建一个新闭包时,会遍历当前函数所有的外部的局部变量,对于每个外部的局部变量,若在上述链表中能找到该变量,则重复使用该打开的upvalue,否则,Lua会创建一个新的打开的upvalue,并插入链表中。当局部变量离开作用域(即超过变量生命周期),这个打开的upvalue就会变成关闭的upvalue,并把它从链表中删除。一旦某个关闭的upvalue不再被任何闭包引用,那么它的存储空间就被回收。

一个函数有可能存取其更外层函数而非直接外层函数的局部变量。在这种情况下,创建闭包时,这个局部变量可能不在栈中。Lua使用flat闭包来处理这种情况。使用flat闭包,无论何时一个函数访问一个外部的局部变量并且该变量不在直接外部函数中,该变量也会进入直接外部函数的闭包中。当一个函数被实例化时,其对应闭包的所有变量要么在直接外部函数的栈中要么在直接外部函数的闭包中。

协程

1
2
3
4
5
6
co = coroutine.create(function () -- 挂起态
print("Hello")
end)
print(coroutine.status(co)) --> suspended
coroutine.resume(co) -->hello (挂起->运行)
print(coroutine.status(co)) --> dead
  • coroutine.create() 创建
  • coroutine.resume() 继续
  • coroutine.status() 查看状态:suspend, running, dead
  • coroutine.yield() 挂起
  • coroutine.wrap() 创建(与create返回协程本身不同,wrap返回函数,创建完直接调用,无法查看状态)
  • coroutine.running() 返回当前正在运行的协程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
-- 生产者消费者
function receive (prod)
local status, value = coroutine.resume(prod)
return value
end
function send (x)
coroutine.yield(x)
end
function producer ()
return coroutine.create(function ()
while true do
local x = io.read() -- produce new value
send(x)
end
end)
end
function filter (prod) -- 加上行号
return coroutine.create(function ()
local line = 1
while true do
local x = receive(prod) -- get new value
x = string.format("%5d %s", line, x)
send(x) -- send it to consumer
line = line + 1
end
end)
end
function consumer (prod)
while true do
local x = receive(prod) -- get new value
io.write(x, "\n") -- consume new value
end
end
p = producer()
f = filter(p)
consumer(f)

数据结构

table是lua中唯一的数据结构,arrays, records, lists, queus, sets都用table实现

  • 链表
1
2
3
4
5
6
7
8
list = nil
list = {next = list, value = 3} -- 插入一个值为3的节点
local l = list
while l do
print(l.value)
l = l.next
end
  • 字符串缓冲
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
function newStack()
return {""}
end
if string.find(_VERSION, "5.3") then --lua5.0以后没有table.getn了
table.getn = function(t)
if t.n then
return t.n
else
local n = 0
for i in pairs(t) do
if type(i) == "number" then
n = math.max(n, i)
end
end
return n
end
end
end
function addString(stack, s)
table.insert(stack, s)
for i = table.getn(stack)-1, 1, -1 do
if string.len(stack[i]) < string.len(stack[i+1]) then
break
end
stack[i] = stack[i] .. table.remove(stack)
end
end
stack = newStack()
addString(stack, "hello ")
addString(stack, "world")
print(stack[table.getn(stack)])

Metatables

Metatables允许我们改变table的行为,例如可以对两个table进行相加的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
a = {1, 2, 3}
b = {7, 8, 9}
mt = {}
setmetatable(a, mt)
setmetatable(b, mt)
function mt.__add(c, d) -- 这里是一个双下划线
local t = {}
for i, v in ipairs(c) do
t[#t + 1] = v
end
for i, v in ipairs(d) do
t[#t + 1] = v
end
return t
end
a = a + b
for i, v in ipairs(a) do
print(v)
end

任何一个表都可以是其他一个表的metatable,一组相关的表可以共享一个metatable(描述他们共同的行为)。一个表也可以是自身的metatable(描述其私有行为)

参考文献

  1. LUA中文教程
  2. 深入理解Lua的闭包一:概念、应用和实现原理(CSDN)