Lua进阶

Posted 两片空白

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lua进阶相关的知识,希望对你有一定的参考价值。

目录

前言

多文件调用

 迭代器

string

lua中string的特点

 string的接口

string.byte (s [, i [, j]])

正则表达式

 元表以及元方法

 Lua的一个语法糖——面向对象的操作

 协程coroutine

 二进制数据的打包和解析


前言

        学会文档查询:官方文档链接——Lua 5.3 参考手册 - 目录

多文件调用

        使用require关键字:

  • 运行指定的文件名。
  • 末尾不带扩展名。
  • 目录层级用'.'分隔。

  • 只会运行一次。
  • 会将调用的返回值返回。

  • 调用的文件从package.path路径中查找。

一般require是用来调用外部库文件的,一般不需要多次调用。

 迭代器

作用是用来遍历出table中所有的值。

一般table的迭代:

 迭代器有两种:

  • ipairs

        ipairs适用于带数字且连续下标的table。

         如果遇到了不连续的数字下标的table,遇到table中没有的数字下标,我们直到返回的nil,此时,ipairs就会停止遍历。

  •  pairs

适用于字符串下标的和不连续的数字下标。

 pairs内部调用的是next函数。

next()函数,会返回传入table值的下一个值,pairs里记录了获取到的值。

 用next也可以快速判断一个table是否为空。

string

lua中string的特点

  • 字符串的存储结构。

        在lua中字符串的存储结构相当于C++中的vector<char>的形式。一个字符一个字符保存在数组中。实际保存的是字符的字节码。

  •  可以保存任意字符的字符串,范围是0x00~0xff,包括不可见字符。特殊的是0x00,在c语言中是以'0'字符结尾,后面的字符就丢失了。在lua中不会,因为里面记录了字符的个数,

  •  字符串有序号,保存正序号和负序号。

正序号,起始位置1开始,后面依次增加。负序号,末尾-1开始,前面依次减少。

 string的接口

  • string.byte (s [, i [, j]])

    返回字符 s[i], s[i+1], ... ,s[j] 的内部数字编码。 i 的默认值是 1 ; j 的默认值是 i

  •  string.char (···)
    接收零或更多的整数。 返回和参数数量相同长度的字符串。 其中每个字符的内部编码值等于对应的参数值。

  •  string.format (formatstring, ···)返回不定数量参数的格式化版本, 格式化串为第一个参数(必须是一个字符串)。和c语言的printf是用没区别。

  •  string.lower (s)
    接收一个字符串,将其中的大写字符都转为小写后返回其副本。 其它的字符串不会更改。 对大写字符的定义取决于当前的区域设置。

正则表达式

        按照一定规则进行字符串的匹配, 它用于模式匹配函数 string.find, string.gmatch, string.gsub, string.match。

        正则的匹配规则为:

  • x(这里 x 不能是 魔法字符 ^$()%.[]*+-? 中的一员) 表示字符 x 自身。
  • .(一个点)可表示任何字符。
  • %a表示任何字母。
  • %c表示任何控制字符。
  • %d表示任何数字。
  • %g表示任何除空白符外的可打印字符。
  • %l表示所有小写字母。
  • %p表示所有标点符号。
  • %s表示所有空白字符。
  • %u表示所有大写字母。
  • %w表示所有字母及数字。
  • %x表示所有 16 进制数字符号。
  • %x(这里的 x 是任意非字母或数字的字符) 表示字符 x。 这是对魔法字符转义的标准方法。 所有非字母或数字的字符 (包括所有标点,也包括非魔法字符) 都可以用前置一个 '%' 放在模式串中表示自身。
  • [set]表示 set 中所有字符的联合。 可以以 '-' 连接,升序书写范围两端的字符来表示一个范围的字符集。 上面提到的 %x 形式也可以在 set 中使用 表示其中的一个元素。 其它出现在 set 中的字符则代表它们自己。 例如,[%w_] (或 [_%w]) 表示所有的字母数字加下划线), [0-7] 表示 8 进制数字, [0-7%l%-] 表示 8 进制数字加小写字母与 '-' 字符。

    交叉使用类和范围的行为未定义。 因此,像 [%a-z] 或 [a-%%] 这样的模式串没有意义。

  • [^set]表示 set 的补集, 其中 set 如上面的解释。

所有单个字母表示的类别(%a%c,等), 若将其字母改为大写,均表示对应的补集。 例如,%S 表示所有非空格的字符。

如何定义字母、空格、或是其他字符组取决于当前的区域设置。 特别注意:[a-z] 未必等价于 %l 。

例如:

全部匹配字符串:直接使用

 部分匹配:使用"[]"

转义,在转义符号前加'%'即可,使用和c语言的'/'相同。

 匹配多个:

        比如:想匹配某字符后面的所有数字,并不能确定数字个数。

模式条目 可以是

  • 单个字符类匹配该类别中任意单个字符;
  • 单个字符类跟一个 '*', 将匹配零或多个该类的字符。 这个条目总是匹配尽可能长的串;
  • 单个字符类跟一个 '+', 将匹配一或更多个该类的字符。 这个条目总是匹配尽可能长的串;
  • 单个字符类跟一个 '-', 将匹配零或更多个该类的字符。 和 '*' 不同, 这个条目总是匹配尽可能短的串;
  • 单个字符类跟一个 '?', 将匹配零或一个该类的字符。 只要有可能,它会匹配一个;
  • %n, 这里的 n 可以从 1 到 9; 这个条目匹配一个等于 n 号捕获物(后面有描述)的子串。
  • %bxy, 这里的 x 和 y 是两个明确的字符; 这个条目匹配以 x 开始 y 结束, 且其中 x 和 y 保持 平衡 的字符串。 意思是,如果从左到右读这个字符串,对每次读到一个 x 就 +1 ,读到一个 y 就 -1, 最终结束处的那个 y 是第一个记数到 0 的 y。 举个例子,条目 %b() 可以匹配到括号平衡的表达式。
  • %f[set], 指 边境模式; 这个条目会匹配到一个位于 set 内某个字符之前的一个空串, 且这个位置的前一个字符不属于 set 。 集合 set 的含义如前面所述。 匹配出的那个空串之开始和结束点的计算就看成该处有个字符 '\\0' 一样。

匹配后面多个字符:

也可以使用'[]',在加上模式条目,可以实现匹配后面的所有'[]'里的字符:

 如果只想得到匹配字符串里的一些字符串:用于string.match,使用括号的形式。并且想取多少个,加多少个括号。

 元表以及元方法

        在文档中的介绍:

        Lua 中的每个值都可以有一个 元表。 这个 元表 就是一个普通的 Lua 表, 它用于定义原始值在特定操作下的行为。 如果你想改变一个值在特定操作下的行为,你可以在它的元表中设置对应域。 例如,当你对非数字值做加操作时, Lua 会检查该值的元表中的 "__add" 域下的函数。 如果能找到,Lua 则调用这个函数来完成加这个操作。

        你可以用 getmetatable 函数 来获取任何值的元表。 Lua 使用直接访问的方式从元表中查询元方法。 

        你可以使用 setmetatable 来替换一张表的元表。

  • __add+ 操作。 如果任何不是数字的值(包括不能转换为数字的字符串)做加法, Lua 就会尝试调用元方法。 首先、Lua 检查第一个操作数(即使它是合法的), 如果这个操作数没有为 "__add" 事件定义元方法, Lua 就会接着检查第二个操作数。 一旦 Lua 找到了元方法, 它将把两个操作数作为参数传入元方法, 元方法的结果(调整为单个值)作为这个操作的结果。 如果找不到元方法,将抛出一个错误。
  • __sub- 操作。 行为和 "add" 操作类似。
  • __mul* 操作。 行为和 "add" 操作类似。
  • __div/ 操作。 行为和 "add" 操作类似。
  • __mod% 操作。 行为和 "add" 操作类似。
  • __pow^ (次方)操作。 行为和 "add" 操作类似。
  • __unm- (取负)操作。 行为和 "add" 操作类似。
  • __idiv// (向下取整除法)操作。 行为和 "add" 操作类似。
  • __band& (按位与)操作。 行为和 "add" 操作类似, 不同的是 Lua 会在任何一个操作数无法转换为整数时 (参见 §3.4.3)尝试取元方法。
  • __bor| (按位或)操作。 行为和 "band" 操作类似。
  • __bxor~ (按位异或)操作。 行为和 "band" 操作类似。
  • __bnot~ (按位非)操作。 行为和 "band" 操作类似。
  • __shl<< (左移)操作。 行为和 "band" 操作类似。
  • __shr>> (右移)操作。 行为和 "band" 操作类似。
  • __concat.. (连接)操作。 行为和 "add" 操作类似, 不同的是 Lua 在任何操作数即不是一个字符串 也不是数字(数字总能转换为对应的字符串)的情况下尝试元方法。
  • __len# (取长度)操作。 如果对象不是字符串,Lua 会尝试它的元方法。 如果有元方法,则调用它并将对象以参数形式传入, 而返回值(被调整为单个)则作为结果。 如果对象是一张表且没有元方法, Lua 使用表的取长度操作(参见 §3.4.7)。 其它情况,均抛出错误。
  • __eq== (等于)操作。 和 "add" 操作行为类似, 不同的是 Lua 仅在两个值都是表或都是完全用户数据 且它们不是同一个对象时才尝试元方法。 调用的结果总会被转换为布尔量。
  • __lt< (小于)操作。 和 "add" 操作行为类似, 不同的是 Lua 仅在两个值不全为整数也不全为字符串时才尝试元方法。 调用的结果总会被转换为布尔量。
  • __le<= (小于等于)操作。 和其它操作不同, 小于等于操作可能用到两个不同的事件。 首先,像 "lt" 操作的行为那样,Lua 在两个操作数中查找 "__le" 元方法。 如果一个元方法都找不到,就会再次查找 "__lt" 事件, 它会假设 a <= b 等价于 not (b < a)。 而其它比较操作符类似,其结果会被转换为布尔量。
  • __index索引 table[key]。 当 table 不是表或是表 table 中不存在 key 这个键时,这个事件被触发。 此时,会读出 table 相应的元方法。

    尽管名字取成这样, 这个事件的元方法其实可以是一个函数也可以是一张表。 如果它是一个函数,则以 table 和 key 作为参数调用它。 如果它是一张表,最终的结果就是以 key 取索引这张表的结果。 (这个索引过程是走常规的流程,而不是直接索引, 所以这次索引有可能引发另一次元方法。)

  • __newindex索引赋值 table[key] = value 。 和索引事件类似,它发生在 table 不是表或是表 table 中不存在 key 这个键的时候。 此时,会读出 table 相应的元方法。

    同索引过程那样, 这个事件的元方法即可以是函数,也可以是一张表。 如果是一个函数, 则以 table、 key、以及 value 为参数传入。 如果是一张表, Lua 对这张表做索引赋值操作。 (这个索引过程是走常规的流程,而不是直接索引赋值, 所以这次索引赋值有可能引发另一次元方法。)

    一旦有了 "newindex" 元方法, Lua 就不再做最初的赋值操作。 (如果有必要,在元方法内部可以调用 rawset 来做赋值。)

  • __call函数调用操作 func(args)。 当 Lua 尝试调用一个非函数的值的时候会触发这个事件 (即 func 不是一个函数)。 查找 func 的元方法, 如果找得到,就调用这个元方法, func 作为第一个参数传入,原来调用的参数(args)后依次排在后面。

使用:

__index: 索引 table[key]。如果table不是表或者不存在key这个键值,元方法会被触发。触发的情况有两种,一种,如果元方法是函数,table和key作为参数传入,一种作为表,结果就是以 key 取索引这张表的结果。

__index写成一个函数: 

 __index写成一张表:

__newindex:索引赋值 table[key] = value 。 和索引事件类似,它发生在 table 不是表或是表 table 中不存在 key 这个键的时候。 此时,会读出 table 相应的元方法。

同索引过程那样, 这个事件的元方法即可以是函数,也可以是一张表。

如果是一个函数, 则以 table、 key、以及 value 为参数传入。

如果是一张表, Lua 对这张表做索引赋值操作。 (这个索引过程是走常规的流程,而不是直接索引赋值, 所以这次索引赋值有可能引发另一次元方法。)

一旦有了 "newindex" 元方法, Lua 就不再做最初的赋值操作。 (如果有必要,在元方法内部可以调用 rawset 来做赋值。)

赋值时触发

注意:在__newindex中赋值只能使用rawset函数,不能使用t[key] = value,这样导致循环调用元方法,导致堆栈溢出,将像拷贝构造函数参数需要使用引用一样。

 Lua的一个语法糖——面向对象的操作

再文档中: Lua 支持的一种语法糖。 像 v:name(args) 这个样子, 被解释成 v.name(v,args), 这里的 v 只会被求值一次。

比如:

 一个简单的Lua面向对象编程:

有一个tag对象

里面

1. 有一个new方法,可以获得一张表。

2. 有put方法,往表里插入数据

3. 有take方法,取出数据

4. 有list方法,列出数据

5. 有clear方法,删除所有数据。

bag = 

mbag = 
    
    put = function(t, item)
        table.insert(t.items, item)
        --t.items:insert(item) table是全局table,有方法insert,不是items的方法,不能使用这种形式
    end,

    take = function(t, item)
        return table.remove(t.items)
    end,

    list = function(t, item)
        return table.concat(t.items, ',')
    end,

    clear = function(t, item)
       t.items = 
    end,
    

mbag["__index"] = mbag --要在外面赋值元方法,可以理解成在mbag里不能使用mbag,因为还没有

function bag.new()
    local t = 
        items = 
    
    setmetatable(t, mbag)--设置元表
    return t
end

 演示:

 

 协程coroutine

官方说明:Lua 支持协程,也叫 协同式多线程。 一个协程在 Lua 中代表了一段独立的执行线程。表示一个协程代表一个线程,一个线程里只运行一个协程,运行其他协程是通过分时复用。并发运行。并不是一起运行的。

接口:

1. coroutine.create (f)

创建一个主体函数为 f 的新协程。 f 必须是一个 Lua 的函数。 返回这个新协程,它是一个类型为 "thread" 的对象。 create 函数只负责新建一个协程并返回其句柄 (一个 thread 类型的对象); 而不会启动该协程。

 2. coroutine.resume (co [, val1, ···])

开始或继续协程 co 的运行。 当你第一次延续一个协程,它会从主体函数处开始运行。 val1, ... 这些值会以参数形式传入主体函数。 如果该协程被让出,resume 会重新启动它; val1, ... 这些参数会作为让出点的返回值。

如果协程运行起来没有错误, resume 返回 true 加上传给 yield 的所有值 (当协程让出), 或是主体函数的所有返回值(当协程中止)。 如果有任何错误发生, resume 返回 false 加错误消息。

调用 coroutine.resume 函数执行一个协程。 第一次调用 coroutine.resume 时,第一个参数应传入 coroutine.create 返回的线程对象,然后协程从其主函数的第一行开始执行。 传递给 coroutine.resume 的其他参数将作为协程主函数的参数传入。 协程启动之后,将一直运行到它终止或 让出

3. coroutine.yield (···)

挂起正在调用的协程的执行。 传递给 yield 的参数都会转为 resume 的额外返回值。yield还会返回协程运行是否正确,正确放回true,错误返回false.

 4. coroutine.wrap (f)

创建一个主体函数为 f 的新协程。 f 必须是一个 Lua 的函数。 返回一个函数, 每次调用该函数都会延续该协程。 传给这个函数的参数都会作为 resume 的额外参数。 和 resume 返回相同的值, 只是没有第一个布尔量。 如果发生任何错误,抛出这个错误。

 相当于将resume返回值封装成了一个函数。

 5. coroutine.status (co)

以字符串形式返回协程 co 的状态: 当协程正在运行(它就是调用 status 的那个) ,返回 "running"; 如果协程调用 yield 挂起或是还没有开始运行,返回 "suspended"; 如果协程是活动的,但并不在运行(即它正在延续其它协程),返回 "normal"; 如果协程运行完主体函数或因错误停止,返回 "dead"

 二进制数据的打包和解析

 lua5.3中有两个接口:

1. string.pack (fmt, v1, v2, ···)

返回一个打包了(即以二进制形式序列化) v1v2 等值的二进制字符串。 字符串 fmt 为打包格式。

2. string.unpack (fmt, s [, pos])

返回以格式 fmt, 打包在字符串 s (参见 string.pack) 中的值。 选项 pos(默认为 1 )标记了从 s 中哪里开始读起。 读完所有的值后,函数返回 s 中第一个未读字节的位置。

格式:

  • <设为小端编码
  • >设为大端编码
  • =大小端遵循本地设置
  • ![n]将最大对齐数设为 n (默认遵循本地对齐设置)
  • b一个有符号字节 (char)
  • B一个无符号字节 (char)
  • h一个有符号 short (本地大小)
  • H一个无符号 short (本地大小)
  • l一个有符号 long (本地大小)
  • L一个无符号 long (本地大小)
  • j一个 lua_Integer
  • J一个 lua_Unsigned
  • T一个 size_t (本地大小)
  • i[n]一个 n 字节长(默认为本地大小)的有符号 int
  • I[n]一个 n 字节长(默认为本地大小)的无符号 int
  • f一个 float (本地大小)
  • d一个 double (本地大小)
  • n一个 lua_Number
  • cnn字节固定长度的字符串
  • z零结尾的字符串
  • s[n]长度加内容的字符串,其长度编码为一个 n 字节(默认是个 size_t) 长的无符号整数。
  • x一个字节的填充
  • Xop按选项 op 的方式对齐(忽略它的其它方面)的一个空条目
  • ' ': (空格)忽略

 

以上是关于Lua进阶的主要内容,如果未能解决你的问题,请参考以下文章

Lua进阶

Lua进阶

Redis进阶应用:Redis+Lua脚本实现复合操作

Redis进阶学习08--多级缓存

本地声明的 lua 函数是不是在每次传递时都会被解析?

Python 实现邮件发送功能(进阶)