Lua介绍
变量
数据类型转换
Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。
lua中的数据类型
数据类型 | 描述 |
---|---|
nil | 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。 |
boolean | 包含两个值:false和true。 |
number | 表示双精度类型的实浮点数。 |
string | 字符串由一对双引号或单引号来表示。 |
function | 由 C 或 Lua 编写的函数。 |
userdata | 表示任意存储在变量中的C数据结构。 |
thread | 表示执行的独立线路,用于执行协同程序。 |
table | Lua 中的表(table)其实是一个”关联数组”(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过”构造表达式”来完成,最简单构造表达式是{},用来创建一个空表。 |
nil 和 false 都会导致条件判断为假,而其它任何值都表示为真
元表基本概念
在 Lua 中,每个值都可以关联一个元表(metatable),元表是一个包含一些特殊字段的表,它定义了该值的一些特殊行为。
- getmetatable() 获取指定值的元表
- setmetatable() 将指定值与元表进行关联
元方法
每个事件的键名用加有 ‘__’ 前缀的字符串来表示; 例如 “add” 操作的键名为字符串 “__add”。
操作 | 描述 |
---|---|
“add” | + 操作。如果任何不是数字的值(包括不能转换为数字的字符串)做加法,Lua 就会尝试调用元方法。 |
“sub” | - 操作。行为和 “add” 操作类似。 |
“mul” | * 操作。行为和 “add” 操作类似。 |
“div” | / 操作。行为和 “add” 操作类似。 |
“mod” | % 操作。行为和 “add” 操作类似。 |
“pow” | ^ (次方)操作。行为和 “add” 操作类似。 |
“unm” | - (取负)操作。行为和 “add” 操作类似。 |
“idiv” | // (向下取整除法)操作。行为和 “add” 操作类似。 |
“band” | & (按位与)操作。行为和 “add” 操作类似,不同的是 Lua 会在任何一个操作数无法转换为整数时尝试取元方法。 |
“bor” | |(按位或)操作。行为和 “band” 操作类似。 |
“bxor” | ~ (按位异或)操作。行为和 “band” 操作类似。 |
“bnot” | ~ (按位非)操作。行为和 “band” 操作类似。 |
“shl” | << (左移)操作。行为和 “band” 操作类似。 |
“shr” | >> (右移)操作。行为和 “band” 操作类似。 |
“concat” | .. (连接)操作。行为和 “add” 操作类似,不同的是 Lua 在任何操作数即不是一个字符串也不是数字的情况下尝试元方法。 |
“len” | # (取长度)操作。如果对象不是字符串,Lua 会尝试它的元方法。如果有元方法,则调用它并将对象以参数形式传入,而返回值(被调整为单个)则作为结果。如果对象是一张表且没有元方法,Lua 使用表的取长度操作。其它情况,均抛出错误。 |
“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 就不再做最初的赋值操作。 |
“call” | 函数调用操作 func(args)。当 Lua 尝试调 |
面向对象
模块与包
require 函数
示例: require "<模块名>"
调用机制:
首先,Lua会检查指定模块是否已经加载过。如果已经加载过,那么Lua会返回已加载模块的缓存结果。
如果模块没有被加载过,Lua会尝试根据指定的模块名搜索可加载的文件。默认情况下,Lua会按照预定义的搜索路径(通常包括当前目录和Lua的标准库路径)来搜索模块文件。
一旦找到模块文件,Lua会执行该文件,并将其中定义的函数、变量等内容作为模块的返回值。这样,就可以通过require函数加载模块并获取模块的功能。
在执行模块文件时,Lua还会创建一个专属于该模块的环境(即一个独立的表)。模块中的函数、变量等只在该环境中有效,不会与全局环境发生冲突。
当模块加载完成后,Lua会将模块的返回值缓存起来,以便下次再次调用require时可以直接返回缓存的结果,而无需重新加载模块文件。
需要注意的是,require函数会自动添加”.lua”后缀来搜索模块文件,因此在使用require时可以省略后缀名。如果需要加载的模块是C语言实现的扩展模块,则不需要省略后缀名。
编码风格
this和self
在 Lua 中,通常没有内置的关键字 this 和 self。这两个词汇不是 Lua 的核心语言特性,而是根据编程习惯或者框架约定来使用的。
通常情况下,在 Lua 中使用 self 来表示当前对象,特别是在面向对象的编程风格中。当你定义一个对象的方法时,第一个参数通常被命名为 self或this,并且这个参数时常指向自身。
可以通过以下是通过元表重载加法运算符的例子体会代码可读性的变化。
不使用this时
1 | local t1 = {1, 2, 3} |
使用this时
1 | local t1 = {1, 2, 3} |
冒号运算符(:)
在 Lua 中,冒号运算符(:)是一种特殊的语法糖,用于简化对象方法的定义和调用。
当你定义一个对象的方法时,可以使用冒号运算符来定义,它会自动将对象传递给方法的第一个参数
1 | local obj = { |
关于Debug
GC
Lua采用了自动内存管理的垃圾收集机制来管理内存的分配和释放。具体来说,Lua实现了一个增量标记-扫描收集器。
垃圾收集器间歇率控制着收集器需要等待多久才开始新的循环,可以通过调整这个值来控制收集器的积极性。另外,垃圾收集器步进倍率控制着收集器运作速度相对于内存分配速度的倍率,可以通过调整这个值来控制收集器的工作速度。
当一个对象不再被访问到时,垃圾收集器会将其标记为死对象,并将其放入一个链表中。在收集完成后,Lua会遍历这个链表,依次调用每个对象的终结器(如果有)进行额外的资源管理工作。
对于完全用户数据(例如C API中的对象),需要使用C API来设置垃圾收集的元方法,即终结器。通过在对象的元表中添加一个以字符串”__gc”为索引的域,可以标记对象需要触发终结器。
在垃圾收集循环的最后阶段,需要触发终结器的对象的终结器会按照标记次序的逆序进行调用。每个终结器的运行可能发生在执行常规代码过程中的任意一刻。
弱表是一种特殊的表,其内部元素为弱引用。垃圾收集器会忽略掉弱引用的对象,当一个对象只被弱引用引用到时,垃圾收集器就会回收这个对象。
弱表可以有弱键、弱值或者键值都是弱引用。对于只有弱键的表,垃圾收集器可以回收其中的键,但会阻止对值所指的对象被回收。对于键值均为弱引用的表,垃圾收集器可以回收其中的任意键和值。对于弱表的弱属性的修改仅在下次收集循环才生效。
需要注意的是,被标记为需要触发终结器的对象在垃圾收集循环中依旧不可达且没有被标记成需要触发终结器时,才会被释放。
总结来说,Lua的垃圾收集机制通过增量标记-扫描收集器实现了自动内存管理。开发者无需手动分配和释放内存,通过设置终结器和使用弱表等机制可以进行额外的资源管理。