ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

《lua程序设计第4版》学习笔记——进阶部分

2022-07-11 09:39:20  阅读:156  来源: 互联网

标签:__ end 函数 -- lua 程序设计 local 进阶


名词解释

高阶函数:以另一个函数为参数的函数
第一类值:意味着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 in exp_list do body 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函数的各个信息:

  1. source:函数定义的位置
  2. short_src:source的精简版本
  3. linedefined:源代码第一行行号
  4. lastlinedefined:最后一行行号
  5. what:说明函数的类型:"Lua"表示lua函数,"C"表示c函数,"main"表示lua语言代码段的主要部分
  6. name:该函数的名称
  7. namewhat:说明上一个字段的含义,比如gloal、local等
  8. nups:该函数上的值的个数
  9. nparms:参数个数
  10. isvararg:是否为可变长参数函数,return bool
  11. activelines:该函数所有活跃行的集合
  12. func:函数本身
  13. currentline:查询的是活跃函数才有的,当前执行的代码行
  14. 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),控制授权函数的运行等

其他

暂无

标签:__,end,函数,--,lua,程序设计,local,进阶
来源: https://www.cnblogs.com/end-emptiness/p/16465334.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有