内容会持续更新,有错误的地方欢迎指正,谢谢!
上一篇文章:Lua菜鸟教程学习笔记二(一些细节)介绍了Lua的一些细节内容,但缺少重难点内容,本文总结一些重难点。
Lua 模块与包
定义模块
模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
Lua 的模块是由变量、函数等已知元素组成的 table,也就是说,模块的结构就是一个 table 的结构,因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或函数。
以下为创建自定义模块 module.lua,文件代码格式如下:
-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}
-- 定义一个常量
module.temp= "这是一个常量"
-- 定义一个函数
function module.func1()
io.write("这是一个公有函数!\n")
end
local function func2()
print("这是一个私有函数!")
end
function module.func3() -- 公有函数
func2()
end
return module
上面的 func2 声明为程序块的局部变量,即表示一个私有函数,因此是不能从外部访问模块里的这个私有函数的,必须通过模块里的公有函数来调用。
调用模块
Lua提供了一个名为require的函数用来加载模块。 执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。
-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")
print(module.constant)
module.func3()
-- 以上代码执行结果为:
这是一个常量
这是一个私有函数!
或者给加载的模块定义一个别名变量,方便调用:
-- 别名变量 m
local m = require("module")
C 包
Lua和C是很容易结合的,使用C为Lua写包。
与Lua中写包不同,C包在使用前必须先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制。
local path = "/usr/local/lua/lib/libluasocket.so"
-- 或者 path = "C:\\windows\\luasocket.dll",这是 Window 平台下
local f = assert(loadlib(path, "luaopen_socket")) -- 两个参数分别是:库的绝对路径和初始化函数
f() -- loadlib函数加载指定的库并且连接到Lua,然而它并不打开库(也就是说没有调用初始化函数),f()才真正打开库
Lua 元表(Metatable)
元表可以很好的简化我们的代码功能,所以了解 Lua 的元表,可以让我们写出更加简单优秀的 Lua 代码。
Lua table无法对两个 table 进行操作,因此 Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。
有两个很重要的函数来处理元表:
setmetatable(table,metatable): 对table设置元表(metatable),如果元表(metatable)中存在__metatable键值,setmetatable会失败
getmetatable(table): 返回对象的元表(metatable)
__index 元方法:用来对表访问
(注意:__是两个下划线)__index 元方法总结:
Lua查找一个表元素时的规则,其实就是如下3个步骤:
- 在表中查找,如果找到,返回该元素,找不到则继续
- 判断该表是否有元表,如果没有元表,返回nil,有元表则继续。
- 判断元表有没有__index方法,如果__index方法为nil,则返回nil;如果__index方法是一个表,则重复1、2、3;如果__index方法是一个函数,则返回该函数的返回值。
mytable = setmetatable({key1 = "value1"}, {
__index = function(mytable, key) -- 如果__index包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数
if key == "key2" then
return "metatablevalue"
else
return nil
end
end
})
print(mytable.key1,mytable.key2)
-- 实例输出结果为:
value1 metatablevalue
-- 我们可以将以上代码简单写成:
mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
print(mytable.key1,mytable.key2)
__newindex 元方法:用来对表更新
__call 元方法:调用一个值时调用
__tostring 元方法:用于修改表的输出行为
Lua 协同程序(coroutine)
Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。
举例如下:
-- coroutine_test.lua 文件
co = coroutine.create(
function(i)
print(i);
end
)
-- 1为函数参数传给co中的function的i
coroutine.resume(co, 1) -- 1
print(coroutine.status(co)) -- dead
print("----------")
co = coroutine.wrap(
function(i)
print(i);
end
)
co(1)
print("----------")
co2 = coroutine.create(
function()
for i=1,10 do
print(i)
if i == 3 then
print(coroutine.status(co2)) --running
print(coroutine.running()) --thread:XXXXXX
end
coroutine.yield()
end
end
)
-- co2中的function无参数,所以只需要传co2
coroutine.resume(co2) --1
coroutine.resume(co2) --2
coroutine.resume(co2) --3
print(coroutine.status(co2)) -- suspended
print(coroutine.running())
print("----------")
以上实例执行输出结果为:
1
dead
----------
1
----------
1
2
3
running
thread: 0x7fb801c05868 false
suspended
thread: 0x7fb801c04c88 true
----------
从coroutine.running可以看出,coroutine在底层实现就是一个线程。当create一个coroutine时就是在新线程中注册了一个事件。当使用resume触发事件时,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件。
resume和yield的配合强大之处在于,resume处于主程中,它将外部状态(数据)传入到协同程序内部;而yield则将内部的状态(数据)返回到主程中。
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("--分割线----")
print("main", coroutine.resume(co, "r")) -- true 11 -9
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- true 10 end
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine
print("---分割线---")
-- 以上实例执行输出结果为:
第一次协同程序执行输出 1 10
foo 函数输出 2
main true 4
--分割线----
第二次协同程序执行输出 r
main true 11 -9
---分割线---
第三次协同程序执行输出 x y
main true 10 结束协同程序
---分割线---
main false cannot resume dead coroutine
---分割线---
上述代码执行的流程如下:
- 调用resume,将协同程序唤醒,resume操作成功返回true,否则返回false;
- 协同程序运行;
- 运行到yield语句;
- yield挂起协同程序,第一次resume返回;
- 第二次resume,再次唤醒协同程序;(注意:此处resume的参数中,除了第一个参数,剩下的参数将作为yield的参数)
- yield返回;
- 协同程序继续运行;
- 如果使用的协同程序继续运行完成后继续调用 resume方法则输出:cannot resume dead coroutine