Lua 特性
- 轻量级:源码2.5万行左右C代码, 方便嵌入进宿主语言(C/C++)
- 可扩展:提供了易于使用的扩展接口和机制, 使用宿主语言提供的功能
- 高效性:运行最快的脚本语言之一
- 可移植:跨平台
Lua 入门
Lua 5.3参考手册
Lua 源码
Lua 语法
变量
作为动态类型语言,变量本身没有类型,赋值决定某一时刻变量的类型。
私有静态变量带local, 公有静态变量不带local。
数据类型:
- nil 为空,无效值,在条件判断中表示false
- boolean 包含两个值:false和true
- number 表示双精度类型的实浮点数
- string 字符串由一对双引号或单引号来表示
- function 由 C 或 Lua 编写的函数
- table Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型
- thread 协程
- userdata 存储在变量中的C数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
print(type(signal)) --nil
signal = true
print(type(signal)) --boolean
signal = 1454
print(type(signal)) --number
signal = "UnionTech"
print(type(signal)) --string
signal = function()
print(type(signal))
end
print(type(signal)) --function
signal = {}
print(type(signal)) --table
signal = coroutine.create(function()
print(type(signal))
end)
print(type(signal)) --coroutine
|
流程控制
if…else
1
2
3
4
5
6
7
8
|
ty_signal = type(signal)
if ty_signal == "coroutine" then
print("signal type is coroutine")
elseif ty_signal == "table" then
print("signal type is table")
else
print("signal type is other")
end
|
while
1
2
3
4
5
6
7
|
ut_companys = {"beijing company", "shanghai company", "nanjing company", "wuxi company", "guangzhou company", "yunfu company", "wuhan company", "chengdu company", "xian company"}
count = 0
while count <= #ut_companys
do
count = count + 1
print("ut_companys[", count, "] is ", ut_companys[count])
end
|
for
1
2
3
|
for i=#ut_companys, 1, -2 do --以2为步长反向遍历
print("num: ", i, "company: ", ut_companys[i])
end
|
表
表作为 Lua 唯一自带的数据结构, 使用简单方便, 兼具数组和 Map 作为容器的功能,通过表可以很容易组成常见的数据结构, 如栈、队列、链表、集合,用 for 循环很容易迭代遍历表数据。
lua 的 table 充当了数组和映射表的双重功能,所以在实现时就考虑了这些,让 table 在做数组使用时尽量少效率惩罚。
lua 是这样做的。它把一个 table 分成数组段和 hash 段两个部分。数字 key 一般放在数组段中,没有初始化过的 key 值全部设置为 nil 。当数字 key 过于离散的时候,部分较大的数字 key 会被移到 hash 段中去。这个分割线是以数组段的利用率不低于 50% 为准。 0 和 负数做 key 时是肯定放在 hash 段中的。
string 和 number 都放在一起做 hash ,分别有各自的算法,但是 hash 的结果都在一个数值段中。hash 段采用闭散列方法,即,所有的值都存在于表中。如果 hash 发生碰撞,额外的数据记在空闲槽位里,而不额外分配空间存放。当整个个表放满后,hash 段会扩大,所有段内的数据将被重新 hash ,重新 hash 后,冲突将大大减少。
这种 table 的实现策略,首先保证的是查找效率。对于把 table 当数组使用时将和 C 数组一样高效。对于 hash 段的值,查找几乎就是计算 hash 值的过程(其中string 的 hash 值是事先计算好保存的),只有在碰撞的时候才会有少许的额外查找时间,而空间也不至于过于浪费。在 hash 表比较满时,插入较容易发生碰撞,这个时候,则需要在表中找到空的插槽。lua 在 table 的结构中记录了一个指针顺次从一头向另一头循序插入来解决空槽的检索。每个槽点在记录 next 指针保存被碰撞的 key 的关联性。
数组
下标从 1 开始
1
2
3
4
5
6
7
8
|
for i, c in ipairs(ut_companys) do
print(string.format("1 UnionTech company: %d %s", i, c))
end
table.sort(ut_companys)
for i=#ut_companys, 1, -1 do
print(string.format("2 UnionTech company: %d %s", i, ut_companys[i]))
end
|
hash map
1
2
3
4
5
6
7
8
9
10
11
|
ut_cptypes = {}
ut_cptypes["adapter"] = {"beijing company", "wuhan company", "guangzhou company"}
ut_cptypes["developer"] = {"beijing company", "wuhan company", "nanjing company", "chengdu company", "xian company", "guangzhou company"}
ut_cptypes["general"] = {"beijing company"}
for ty, cps in pairs(ut_cptypes) do
for i, cp in ipairs(cps) do
print(string.format("3 UnionTech companys: type:%s identifier:%s company:%s", ty, i, cp))
end
end
|
函数
在 Lua 中,函数也是第一类型值,可赋值给变量,也可以在函数体内定义并使用函数,或者是直接使用匿名匿名函数。
多重返回值
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
|
ut_types = {"adapter", "developer", "general"}
function company_types(cp, cptypes)
local adpt, dvlp, genl = nil, nil, nil
for i, ty in ipairs(ut_types) do
for _, _cp in ipairs(cptypes[ty]) do
if _cp == cp then
if i == 1 then
adpt = true
elseif i == 2 then
dvlp = true
elseif i == 3 then
genl = true
end
break
end
end
end
return adpt, dvlp, genl
end
cp = "wuhan company"
types = {company_types(cp, ut_cptypes)}
for i, ty in ipairs(types) do
if ty then
print(string.format("%s is %s", cp, ut_types[i]))
end
end
|
可变参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
function printf(str, ...)
print(string.format(str, ...))
end
function add_companys(...)
local newcps = {...}
local num = #newcps
for _, cp in ipairs(newcps) do
table.insert(ut_companys, cp)
end
return ut_companys, num
end
_, _ = add_companys("changsha company", "zhengzhou company", "hefei company")
for i=1, #ut_companys do
--print(string.format("4 UnionTech company: %d %s", i, ut_companys[i]))
printf("4 UnionTech company: %d %s", i, ut_companys[i])
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
|
function all_companys(cps)
local companys, n = {}, 0
for _, v in ipairs(cps) do
table.insert(companys, v)
end
return function()
n = n + 1
if n > #companys then
return ""
else
return companys[n]
end
end
end
get_company = all_companys(ut_companys)
while true
do
cp = get_company()
if cp == "" then
break
else
printf("get company: %s", cp)
end
end
|
协程(coroutine)
Lua ,也叫 协同式多线程。与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
与多线程系统中的线程的区别在于, 协程仅在显式调用一个让出(yield)函数时才挂起当前的执行。协程可以视为程序的执行单位,和线程不同,线程是抢占式的(因为是在单线程内),多条线程是并行时运行的,而协程则不是,协程是协同式的,比如有三个协程按顺序先后创建 coA、coB、coC,那么在没有任意一条协程主动挂起(yield)的情况下,执行顺序则是 coA 执行完,在执行 coB,然后再执行 coC。也就是说,除非有协程主动要求挂起,否则必须等当前协程执行完,再去执行下面一个创建的协程。比如说,coA 执行完,接着就是执行 coB,此时 coB 挂起,那么直接执行 coC,coC 执行完以后,如果 coB 被唤醒了,则接着上次开始阻塞的部分继续执行余下的逻辑。
调用函数 coroutine.create 可创建一个协程。 其唯一的参数是该协程的主函数。 create 函数只负责新建一个协程并返回其句柄 (一个 thread 类型的对象); 而不会启动该协程。
调用 coroutine.resume 函数执行一个协程。 第一次调用 coroutine.resume 时,第一个参数应传入 coroutine.create 返回的线程对象,然后协程从其主函数的第一行开始执行。 传递给 coroutine.resume 的其他参数将作为协程主函数的参数传入。 协程启动之后,将一直运行到它终止或 让出。
协程的运行可能被两种方式终止: 正常途径是主函数返回 (显式返回或运行完最后一条指令); 非正常途径是发生了一个未被捕获的错误。 对于正常结束, coroutine.resume 将返回 true, 并接上协程主函数的返回值。 当错误发生时, coroutine.resume 将返回 false 与错误消息。
通过调用 coroutine.yield 使协程暂停执行,让出执行权。 协程让出时,对应的最近 coroutine.resume 函数会立刻返回,即使该让出操作发生在内嵌函数调用中 (即不在主函数,但在主函数直接或间接调用的函数内部)。 在协程让出的情况下, coroutine.resume 也会返回 true, 并加上传给 coroutine.yield 的参数。 当下次重启同一个协程时, 协程会接着从让出点继续执行。 此时,此前让出点处对 coroutine.yield 的调用 会返回,返回值为传给 coroutine.resume 的第一个参数之外的其他参数。
与 coroutine.create 类似, coroutine.wrap 函数也会创建一个协程。 不同之处在于,它不返回协程本身,而是返回一个函数。 调用这个函数将启动该协程。 传递给该函数的任何参数均当作 coroutine.resume 的额外参数。 coroutine.wrap 返回 coroutine.resume 的所有返回值,除了第一个返回值(布尔型的错误码)。 和 coroutine.resume 不同, coroutine.wrap 不会捕获错误; 而是将任何错误都传播给调用者。
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
|
-- 下面的代码展示了一个协程工作的范例:
function foo (a)
print("foo", a)
return coroutine.yield(2*a)
end
co = coroutine.create(function (a,b)
print("co-body", a, b)
local r = foo(a+1)
print("co-body", r)
local r, s = coroutine.yield(a+b, a-b)
print("co-body", r, s)
return b, "end"
end)
print("main", coroutine.resume(co, 1, 10))
print("main", coroutine.resume(co, "r"))
print("main", coroutine.resume(co, "x", "y"))
print("main", coroutine.resume(co, "x", "y"))
-- 当你运行它,将产生下列输出:
co-body 1 10
foo 2
main true 4
co-body r
main true 11 -9
co-body x y
main true 10 end
main false cannot resume dead coroutine
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
function foo (a)
print("foo 函数输出", a)
return coroutine.yield(2 * a) -- 返回 2*a 的值
end
co = coroutine.create(function (a , b)
print("第一次协同程序执行输出", a, b) -- co-body 1 10
local r = foo(a + 1)
print("第二次协同程序执行输出", r)
local r, s = coroutine.yield(a + b, a - b) -- a,b的值为第一次调用协同程序时传入
print("第三次协同程序执行输出", r, s)
return b, "结束协同程序" -- b的值为第二次调用协同程序时传入
end)
print("main", coroutine.resume(co, 1, 10)) -- true, 4
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
--resume将主协程数据传入次协程, yield将次协程中数据传回主协程
|
__index
当你通过键来访问 table 的时候,如果这个键没有值,那么 Lua 就会寻找该 table 的 metatable(假定有 metatable)中的 __index 键。如果 __index 包含一个表格,Lua 会在表格中查找相应的键(元素是方法也是如此)。
1
2
3
4
5
6
|
> other = { foo = 3 }
> t = setmetatable({}, { __index = other })
> t.foo
3
> t.bar
nil
|
Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:
- 在表中查找,如果找到,返回该元素,找不到则继续
- 判断该表是否有元表,如果没有元表,返回 nil,有元表则继续
- 判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。
__newindex
__newindex 元方法用来对表更新,__index 则用来对表访问 。当你给表的一个缺少的索引赋值,解释器就会查找 __newindex 元方法,如果存在则调用这个函数而不进行赋值操作。
__call
__call 元方法在 Lua 调用一个值时调用。
本质上来说就是存放元方法的表结构, 通过元表实现对表中数据和行为的改变。
Lua 查找一个表元素时的规则,其实就是如下 3 个步骤:
- 在表中查找,如果找到,返回该元素,找不到则继续
- 判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
- 判断元表有没有 __index 方法
- 如果 __index 方法为 nil,则返回 nil
- 如果 __index 方法是一个表,则重复 1、2、3
- 如果 __index 方法是一个函数,则返回该函数的返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
father = {
colourofskin = "yellow",
weight = 70,
work = "programming",
otherwork = function()
print "do housework"
end
}
father.__index = father
son = {
weight = 50,
like = "basketball"
}
setmetatable(son, father)
printf("weight:%d like:%s work:%s colourofskin:%s ", son.weight, son.like, son.work, son.colourofskin)
son.otherwork()
|
面向对象
因为 lua 本身不是面向对象的语言,在lua中,通过 table 和 function 来模拟一个对象,用 metatable 来模拟面向对象中的继承,但是在使用的时候需要考虑 lua 作为脚本语言,变量的类型随所赋值类型而改变
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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
--父类
rect = {
area = 0,
length = 0,
width = 0,
}
function rect:getArea()
if self.area == 0 then
self.area = self.length * self.width
end
return self.area
end
function rect:getLength()
return self.length
end
function rect:new(leng, wid)
self.length = leng
self.width = wid
return self
end
--子类
cuboid = {
volume = 0,
height = 0,
}
function cuboid:getVolume()
if self.volume == 0 then
self.volume = self.height * self:getArea()
end
return self.volume
end
function cuboid:new(_rect, _height)
setmetatable(self, _rect)
_rect.__index = _rect
self.height = _height
return self
end
rect1 = rect:new(5, 10)
print("rect1 rectangle:", rect1:getArea())
cuboid1 = cuboid:new(rect1, 2)
print("cuboid1 volume: ", cuboid1:getVolume())
print("cuboid1 rectangle: ", cuboid1:getArea()) --子类调用父类方法getArea
print("cuboid1 length function: ", cuboid1:getLength()) --子类调用父类方法getLength
print("cuboid1 length variable: ", cuboid1.length) --子类使用父类变量length
--重写子类接口getArea, lua中没有重载
function cuboid:getArea()
return 2 * (self.height * self.length + self.height * self.width + self.length * self.width)
end
cuboid2 = cuboid:new(rect1, 2)
print("cuboid2 function: getArea: ", cuboid2:getArea()) --调用子类重写的方法getArea
print("cuboid2 base function: getArea: ", getmetatable(cuboid2):getArea()) --显示调用父类方法getArea
|
模块与 C 包
模块类似封装库,有利于代码复用,降低耦合,提供被调用的 API
文件名为 module.lua
, 定义一个名为 module
的模块
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
module = {}
module.constant = "这是一个常量"
function module.func1()
io.write("这是一个公有函数!\n")
end
local function func2()
print("这是一个私有函数!")
end
function module.func3()
func2()
end
return module
|
在其他模块中调用 module
模块
1
2
|
local m = require("module")
print(m.constant)
|
与 Lua 中写包不同,C 包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制
Lua 在一个叫 loadlib 的函数内提供了所有的动态连接的功能
Lua 标准库
标准库中接口可直接使用不需要 require
常用标准库:
- math 数学计算
- table 表结构数据处理
- string 字符串处理
- os 系统库函数
- io 文件读写
- coroutine 协程库
- debug 调式器
Lua 虚拟机
脚本语言没有像编译型语言那样直接编译为机器能识别的机器代码,这意味着解释性脚本语言与编译型语言的区别
由于每个脚本语言都有自己的一套字节码,与具体的硬件平台无关,所以无需修改脚本代码,就能运行在各个平台上
硬件、软件平台的差异都由语言自身的虚拟机解决。由于脚本语言的字节码需要由虚拟机执行,而不像机器代码那样能够直接执行,所以运行速度比编译型语言差不少
有了虚拟机这个中间层,同样的代码可以不经修改就运行在不同的操作系统、硬件平台上。Java、Python 都是基于虚拟机的编程语言,Lua 同样也是这样