《lua程序设计第4版》学习笔记——进阶部分
2022/7/12 1:31:14
本文主要是介绍《lua程序设计第4版》学习笔记——进阶部分,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
名词解释
高阶函数:以另一个函数为参数的函数
第一类值:意味着lua语言中的函数和其他常见类型的值同等权限(比如保存到变量、放在表中)
闭包
递归函数定义问题
在编译函数体中的函数时,如果当前函数未定义,会去找全局函数。所以在定义递归函数时,要注意先定义
-- 错误的编写 local fact = function(n) return n == 0 and 1 or fact(n - 1) -- 因为局部的fact还没定义,所以去找全局的fact end -- 正确的编写 local fact fact = function(n) return n == 0 and 1 or fact(n - 1) end
词法定界
当编写一个被其他函数B包含的函数A时,被包含的函数A可以访问包含其的函数B的所有局部变量
function newCouner() local count = 0 return function () count = count + 1 return count end end
上面的count是局部变量,理论上新的函数体是访问不到的,但是由于闭包的机制,函数可以逃逸变量的定界范围
模式匹配
函数
- string.find
- string.match
- string.gsub
- string.gmatch
模式
符号 | 含义 |
---|---|
. | 任意字符 |
%a | 字母 |
%c | 控制字符 |
%d | 数字 |
%g | 除空格外的可打印字符 |
%l | 小写字符 |
%p | 标点字符 |
%s | 空白字符 |
%u | 大写字母 |
%w | 字母和数字 |
%x | 十六进制数字 |
其他字符
符号 | 含义 |
---|---|
+ | 重复一次或多次 |
* | 重复零次或多次 |
- | 重复一次或多次(最小匹配) |
? | 可选(出现零次或一次) |
% | 转移字符,比如%% 表示% 这个字符 |
^ | 放在开头,表示某个字符集的补集。比如[^0-7] |
$ | 放在结尾,表示匹配到目标字符串的结尾 |
[ ] | 表示字符集 |
( ) | 表示捕获 |
捕获
把需要捕获的内容放到小括号中
pair = "name = anna" k, v = string.match(pair, "(%a+)%s*=&s*(%a+)") print(k, v) --> name anna
样例
-- 替换 string.gsub("hello Lua!", "%a", "%0-%0") --查找 string.find("123123", "^[+-]?%d+$")
日期和时间
-- 当前时间戳 os.time() t = os.date("*t") -- 固定格式时间 os.date("%Y-%m-%d %H:%M:%S", os.time()) -- 生成指定时间 t = os.date("*t") t.day = t.day + 40 os.time(t) -- t: {year, month, day, hour, min, sec} -- 计算时间差值 os.difftime(a, b) -- 计算程序耗时(cpu时间) local x = os.clock() mainWork() print(os.clock() - x)
编译、执行和错误
编译
- dofile: 从文件运行lua代码
- loadfile: 从文件读取并编译lua代码
- load: 字符串读取并编译lua代码
f = load("i = i + 1") i = 0 assert(f()); print(i) --> 1 assert(f()); print(i) --> 2
预编译代码
$ luac -o prog.lc prog.lua $ lua prog.lc
上面编译代码的函数,用.lc结尾的文件,也都是正确的
预编译的特点:
1、不一定比源代码小
2、但是加载更快
3、没办法修改源码,防止hack
错误处理和异常
pcall函数,相当于其他语言的try-catch
local ok, msg = pcall( function () some code if unexpected_condition then error() end some code print(a[i]) -- 潜在错误,a可能不是一个表 some code end ) if ok then pass else pass end
error函数,抛出一个错误。第二个参数level表示:在函数的调用层级中的哪层函数报告错误
function foo(str) -- 第一层是foo函数自己,第二层是调用foo函数的地方 error(str, 2) end foo({11})
xpcall函数,类似pcall,但是他的第二个参数是一个消息处理函数,在这个函数里面可以去获取堆栈信息
xpcall(error code, function () debug.traceback() -- debug.debug() end)
模块和包
模块导入函数:require
local m = require('math')
加载顺序:
1、先在package.loaded
中检查是否被加载
1.1、表的形式是package.loaded.(modname)
,比如math模块,就是package.loaded.math
2、如果未加载,搜索对应的lua文件(搜索的路径由package.path
指定)
2.1、如果找到了,调用loadfile
3、如果没找到,则搜索C标准库(路径由package.cpath
指定)
3.1、如果找到了,调用loadlib
,这个函数会查找luaopen_(modname)
的函数
编写模块
local M = {} function M.func() end return M -- package.loader[...] = M
如果不想要最后的return,可以直接给package.loader赋值
子模块
函数require会将点转换为另一个字符,通常是操作系统的目录分隔符
比如调用require "a.b"
,会打开a/b.lua
文件
这个是编译时配置的
迭代器和泛型for
泛型for
语法:
for
var_list
inexp_list
dobody
end
- var_list:控制变量,一般不超过三个,因为for最多返回3个
- exp_list:表达式列表。只有最后一个能够返回多个值,而且只会保留3个值,多余的会舍弃,少的补nil
无状态迭代器
定义:自身不保存任何装填的迭代器,可以在多个循环中使用同一个无状态迭代器,从而避免创建新闭包的开销
比如:ipairs
ps:pairs和ipairs类似,但是pairs用的是基础函数next
元表和元函数
lua中无法对两个table进行操作(比如相加)。
因此lua提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。
类似c++在类中重载operator+等操作符的操作
local mt = {} mt.__add = function(a, b) local res = {} for _, v in pairs(a) do table.insert(res, v) end for _, v in pairs(b) do table.insert(res, v) end return res end s1 = {10, 20, 30, 40} s2 = {30, 1} setmetatable(s1, mt) setmetatable(s2, mt) s3 = s1 + s2 for k, v in pairs(s3) do print(k .. " : " .. v) end --[[ 结果: 1 : 10 2 : 20 3 : 30 4 : 40 5 : 30 6 : 1 ]]--
元方法
元方法 | 对应操作符 |
---|---|
__add | + |
__sub | - |
__mul | * |
__div | / |
__idiv | |
__mod | % |
__unm | 负数:- |
__concat | 连接运算符:. |
__eq | == |
__lt | < |
__le | <= |
__pow | 幂运算 |
__band | 按位与 |
__bor | 按位或 |
__bxor | 按位异或 |
__bnot | 按位取反 |
__shl | 左移 |
__shr | 右移 |
__tostring | 重点,重载元表的输出 |
__pairs | lua 5.2以上,对应函数pairs |
__index | 查找字段:[] |
__newindex | 对一个表中不存在的索引赋值 |
tostring本质是先检查元方法__tostring,如果有就调用这个
面向对象
成员函数调用
声明和调用的时候,如果用点,第一个参数需要是self
如果用冒号,可以隐藏
function Account.work(self, a) do end function Account:work(a) do end
冒号的作用就是在一个方法调用中增加一个额外的实参
多重继承
用元方法查找实现
function createClass(...) local c = {} local parents = {...} -- 父类列表 setmetatable(c, { __index = function(t, k) for i = 1, #parents do local v = parent[i][k] if v then return v end end end}) -- 将c作为其实例的元表 c.__index = c end
环境
全局变量:_G
lua中的全局变量是存在_G表中的,而且全局变量不需要声明就可以使用,如果手滑打错字很难发现bug,所以可以通过搜索这张表来判断全局变量是否存在
if rawget(_G, var) == nil then end
_ENV
当前环境表,出现调用未声明的变量会优先在这个表里的找。也可以对这个表赋值,从而做些骚操作
local M = {} _ENV = M function add(a, b) return new(a, b) end
上面这个例子中,调用add,等同于调用M.new
_G 和 _ENV 的关系
通常情况下,_G 和 _ENV 指向的是同一个表,但它们是两个不同的实体。 _ENV 是一个局部变量,所有对“全局变量”的访问实际上都是访问 _ENV 。 _G则是一个在任何情况下都没有任何特殊状态的全局变量。按照定义, _ENV永远指向的是当前的环境 _G在没有手动改变其值的情况下指向的是全局环境。
垃圾收集(GC)
GC流程
标记 -> 清理 -> 清除 -> 析构
1、从根节点遍历所有对象,遍历到的标记为活跃
2、清理阶段,先处理析构器和弱引用表,然后把需要析构的函数放在单独的列表里(复苏的对象就是在这个过程被放在一个单独的列表中的)
3、清除阶段,没有活跃的对象,全部回收
4、调用清理阶段被分离出来对象的析构器
Lua 5.1 使用了增量式垃圾收集器,不必每次停机清理,到达上限时,只会执行一小步
Lua 5.2 引入了紧急垃圾收集,当内存分配失败时,Lua语言会强制执行一次完整的垃圾收集,然后再次尝试分配
弱引用
由__mode字段决定,"k"表示键是弱引用的,"v"表示值是弱引用的
a = {} mt = {__mode = "k"} setmetatable(a, mt) key = {} a[key] = 1 key = {} a[key] = 2 collectgarbage() -- 强制垃圾回收 for k, v in pairs(a) do print(v) end --> 2
如上,因为第一个键已经被回收了,所以表中也没有对应的项了
析构器
由__gc方法实现,当该对象被回收时会被调用
o = {x = "hi"} setmetatable(o, {__gc = function(o) print(o.x) end}) o = nil collectgarbage() --> hi
复苏
如果在a的析构函数中,访问了b的话,那这个b会被加入到全局变量中,导致b在本次垃圾回收中,无法被回收,在调用第二次gc时,才会回收b,这种情况叫做复苏,编程时需要注意。
协程
所在函数:表coroutine
lua语言提供的是非对称协程,用两个函数控制协程,一个是挂起,一个是恢复
对称协程是只用一个函数,来切换两个协程之间的控制权
使用方法
- coroutine.create(func): 创建协程
- coroutine.status(co): 查看协程状态:挂起、运行、正常和死亡
- coroutine.resume(co): 恢复协程
- coroutine.yield(): 挂起协程
反射
反射是程序用来检查和修改其自身某些部分的能力
虽然lua有部分反射机制,但是还是缺失一些内容,这些内容被调试库
所填补
自省机制
debug.getinfo(foo),可以显示foo函数的各个信息:
- source:函数定义的位置
- short_src:source的精简版本
- linedefined:源代码第一行行号
- lastlinedefined:最后一行行号
- what:说明函数的类型:"Lua"表示lua函数,"C"表示c函数,"main"表示lua语言代码段的主要部分
- name:该函数的名称
- namewhat:说明上一个字段的含义,比如gloal、local等
- nups:该函数上的值的个数
- nparms:参数个数
- isvararg:是否为可变长参数函数,return bool
- activelines:该函数所有活跃行的集合
- func:函数本身
- currentline:查询的是活跃函数才有的,当前执行的代码行
- istailcall:查询的是活跃函数才有的,表示函数是否是被尾调用所调用的
第二个参数,为了更好的性能,而选择自己想要的信息返回:
- n:选择6、7
- f:选择12
- S:选择1、2、3、4、5
- l:选择13
- L:选择11
- u:选择8、9、10
访问变量
访问局部变量:debug.getlocal(函数的栈层次, 变量索引)
访问非局部变量:debug.getupvalue(闭包函数, 变量索引)
这两个函数都有对应的set函数
钩子
在特定的时间发生时,调用注册的钩子函数
function trace(event, line) local s = debug.getinfo(2).short_src print(s .. ":" .. line) end debug.sethook(trace, "l") local a = 1 -- 会输出文件名+行号
sethook的第二个参数表示监控的事件:
- c:调用函数的call事件
- r:函数返回的return事件
- l:执行一行新代码的line事件
- 一个计数器:执行完指定数量的指令后产生的count事件
- 不加第二参数:关闭钩子
钩子和debug.debug函数是一个不错的搭配
通过设置call事件的钩子,我们可以监控函数的调用:debug.sethook(hook, "c")
比如可以监听函数调用的次数,控制函数的调用次数(创造沙盒防止dos),控制授权函数的运行等
其他
暂无
这篇关于《lua程序设计第4版》学习笔记——进阶部分的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-05-19永别了,微服务架构!
- 2024-05-15鸿蒙生态设备数量超8亿台
- 2024-05-13TiDB + ES:转转业财系统亿级数据存储优化实践
- 2024-05-09“2024鸿蒙零基础快速实战-仿抖音App开发(ArkTS版)”实战课程已上线
- 2024-05-09聊聊如何通过arthas-tunnel-server来远程管理所有需要arthas监控的应用
- 2024-05-09log4j2这么配就对了
- 2024-05-09nginx修改Content-Type
- 2024-05-09Redis多数据源,看这篇就够了
- 2024-05-09Google Chrome驱动程序 124.0.6367.62(正式版本)去哪下载?
- 2024-05-09有没有大佬知道这种数据应该怎么抓取呀?