Lua 5.0 ~ 5.4 学习笔记
🌳1. Begin
🌳1.1 chunk
Chunk 是一系列语句,Lua 执行的每一块语句,比如一个文件或者交互模式下的每一行都是一个 Chunk。
每个语句结尾的分号(;)是可选的,但如果同一行有多个语句最好用;分开(but valid)
Chunk可以很大,在 Lua 中几个 MByte 的 Chunk 是很常见的
交互模式下 键入文件结束符可以退出交互模式(Ctrl-D in Unix, Ctrl-Z in DOS/Windows),或者调用 OS 库的 os.exit()函数也可以退出
prompt> lua -la -lb
命令首先在一个 Chunk 内先运行 a 然后运行 b。(注意:-l 选项会调用 require,将会在指定的目录下搜索文件,如果环境变量没有设好,上面的命令可能不能正确运行。)
lua -i -la -lb
将在一个 Chunk 内先运行 a 然后运行 b,最后直接进入交互模式
最好不要使用下划线加大写字母的标示符,因为 Lua 的保留字也是这样的。
🌳1.4 lua command line
-
-e:直接将命令传入 Lua
prompt> lua -e “print(math.sin(12))” --> -0.53657291800043
-
-l:加载一个文件.
-
-i:进入交互模式.
_PROMPT 内置变量作为交互模式的提示符prompt> lua -i -e “_PROMPT=’ lua> '”
lua>
Lua 的运行过程,在运行参数之前,Lua 会查找环境变量 LUA_INIT
的值,
- 如果变量存在并且
值
为@filename
,Lua 将加载指定文件。 - 如果变量存在但
不是以@开头
,Lua假定 filename 为 Lua 代码文件并且运行他。
全局变量 arg
存放 Lua 的命令行参数。在运行以前,Lua 使用所有参数构造 arg 表。
- 脚本名索引为 0,
- 脚本的参数从 1 开始增加。
- 脚本前面的参数从-1 开始减少
prompt> lua -e “sin=math.sin” script a b
arg 表如下:
1 | arg[-3] = "lua" |
🌳2. lua basic type
Lua 是动态类型语言,变量不要类型定义。
Lua 中有8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table
。
函数 type
可以测试给定变量或者值的类型。
在控制结构的条件中除了 false
和 nil
为假,其他值都为真。
所以 Lua 认为 0
和空串
都是真。
number
表示实数,Lua 中没有整数。
note: 一般有个错误的看法 CPU 运算浮点数比整数慢。事实不是如此,用实数代替整数不会有什么误差(除非数字大于 100,000,000,000,000)。
Lua的 numbers 可以处理任何长整数不用担心误差。
note: 你也可以在编译 Lua
的时候使用长整型或者单精度浮点型
代替 numbers,在一些平台硬件不支持浮点数
的情况下这个特性是非常有用的,具体的情况请参考 Lua 发布版所附的详细说明。
🌳2.4 string
字符的序列, lua 是 8 位字节,所以字符串可以包含任何数值字符,包括嵌入的 0。
这意味着你可以存储任意的二进制数据在一个字符串里。
1 | a = "one string" |
Lua 可以高效的处理长字符串,1M
的 string 在 Lua 中是很常见的。可以使用单引号
或者双引号
表示字符串
还可以在字符串中使用\ddd(ddd 为三位十进制数字)方式表示字母。
“alo\n123"”
‘\97lo\10\04923"’
是相同的
还可以使用[[...]]
表示字符串。
这种形式的字符串可以包含多行
也,可以嵌套且不会解释转义序列
,如果第一个字符
是换行符
会被自动忽略掉。
种形式的字符串用来包含一段代码是非常方便的。
1 | page = [[ |
运行时,Lua 会自动在 string 和 numbers 之间自动进行类型转换,当一个字符串使
用算术操作符时,string 就会被转成数字。
1 | print("10" + 1) --> 11 |
..
在 Lua 中是字符串连接符,当在一个数字
后面写..
时,必须加上空格
以防止被解释错
显式将 string 转成数字可以使用函数 tonumber()
,如果 string 不是正确的数字该函数将返回 nil
。
可以调用tostring()
将数字转成字符串,这种转换一直有效
🌳2.5 Function
函数是第一类值
(和其他变量相同),意味着
- 函数可以存储在变量中,
- 可以作为函数的参数,
- 也可以作为函数的返回值。
🌳2.6 Userdata and Threads
- userdata 可以将 C 数据存放在 Lua 变量中,
- userdata 在 Lua 中预定义操作
赋值
和相等比较
🌳3. expression 表达式
🌳3.2 Relational operators
关系运算符
< > <= >= == ~=
- Lua 通过
引用
比较tables、userdata、functions
。也就是说当且仅当两者表示同一个对象时相等。 - Lua 比较数字按传统的数字大小进行,
- 比较字符串按字母的顺序进行,但是字母顺序依赖于本地环境
🌳3.3 logical operator 逻辑运算符
and or not
一个很实用的技巧:如果 x 为 false 或者 nil 则给 x 赋初始值 v
x = x or v
C 语言中的三元运算符
(a and b) or c ==> a ? b : c
🌳3.5 operator priority 优先级
从高到低的顺序:
1 | ^ |
除了^和…外所有的二元运算符都是左连接
的。
1 | a+i < b/2+1 <--> (a+i) < ((b/2)+1) |
🌳3.6 table constructor 表的构造
最简单的构造函数是{}
,用来创建一个空表
。
使用 table 构造一个 list:
1 | list = nil |
嵌套构造函数
1 | polyline = {color="blue", thickness=2, npoints=4, |
- 不能使用负索引初始化一个表中元素,
- 字符串索引也不能被恰当的表示。
1 | opnames = { |
- 注意:不推荐数组下标
从 0 开始
,否则很多标准库不能使用
。 - 在构造函数的
最后的
","是可选的,可以方便以后的扩展。 - 在构造函数中域分隔符逗号(",")可以用分号(";")替代,通常我们使用分号用来分割不同类型的表元素。
如果真的想要数组下标从 0 开始:
days = {[0]=“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”}
{x=10, y=45; “one”, “two”, “three”}
🌳4. basic syntax 基本语法
a, b = 10, 2x <–> a=10; b=2x
遇到赋值语句 Lua 会先计算右边所有的值然后再执行赋值操作,所以我们可以这样
进行交换变量的值:
1 | x, y = y, x -- swap 'x' for 'y' |
当变量个数和值的个数不一致时,Lua 会一直以变量个数为基础采取以下策略:
- a. 变量个数>值的个数 按变量个数补足 nil
- b. 变量个数<值的个数 多余的值会被忽略
1 | a, b, c = 0, 1 |
🌳4.2 local variable & code block 局部变量 代码块
使用 local
创建一个局部变量,与全局变量不同,局部变量只在被声明的那个代码块内有效。代码块:指一个控制结构
内,一个函数体
,或者一个 chunk
(变量被声明的那个文件或者文本串)
应该尽可能的使用局部变量,有两个好处:
- 避免命名冲突
- 访问局部变量的速度比全局变量更快.
do … end
1 | if conditions then |
🌳4.3 break return
有时候为了调试或者其他目的需要在 block 的中间使用 return 或者 break,可以显式的使用 do…end 来实现:
1 | function foo () |
🌳5. function
调用函数的时候,如果参数列表为空,必须使用()表明是函数调用。
当函数只有一个参数
并且这个参数是字符串
或者表构造
的时候,()是可选的:
1 | print "Hello World" --> print("Hello World") |
string.find,其返回匹配串“开始和结束的下标”
(如果不存在匹配串返回 nil
)。
1 | s, e = string.find("hello Lua users", "Lua") |
🌳5.1 multi result return
返回多个结果值
-
作为表达式调用函数
- 当调用
作为表达式最后一个参数
或者仅有一个参数
时,根据变量个数函数尽可能多地返回多个值,不足补 nil,超出舍去。 - 其他情况下,函数调用
仅返回第一个值
(如果没有返回值为 nil)
1
2
3
4
5
6function foo0 () end -- returns no results
function foo1 () return 'a' end -- returns 1 result
function foo2 () return 'a','b' end -- returns 2 results
x,y = foo2(), 20 -- x='a', y=20
x,y = foo0(), 20, 30 -- x='nil', y=20, 30 is discarded - 当调用
-
作为函数参数调用
1
2print(foo2(), 1) --> a 1
print(foo2() .. "x") --> ax -
在表构造函数 调用
1
a = {foo0(), foo2(), 4} -- a[1] = nil, a[2] = 'a', a[3] = 4
-
return f()这种类型的返回 f()返回的
所有值
-
可以使用圆括号强制使调用返回一个值。
1
2
3print((foo0())) --> nil
print((foo1())) --> a
print((foo2())) --> a
函数多值返回的特殊函数 unpack,接受一个数组作为输入参数,返回数组的所有元素。
unpack 被用来实现范型调用机制,在 C 语言中可以使用函数指针调用可变的函数,可以声明参数可变的函数,但不能两者同时可变。
在 Lua 中如果你想调用可变参数的可变函数只需要这样
f(unpack(a))
预定义的 unpack 函数是用 C 语言实现的,我们也可以用 Lua 来完成:
1 | function unpack(t, i) |
🌳5.2 variable parameter 可变参数
Lua 函数可以接受可变数目的参数,和 C 语言类似在函数参数列表中使用三点(...)
表示函数有可变的参数。
Lua 将函数的参数放在一个叫 arg
的表中,除了参数以外,arg表
中还有一个域 n
表示参数的个数。
1 | function g (a, b, ...) end |
如果我们只想要 string.find 返回的第二个值:一个典型的方法是使用虚变量(下划线)
local _, x = string.find(s, p)
🌳5.3 named parameter 命名参数
1 | function rename (arg) |
🌳6. function plus 再论函数
note: Lua 中的函数是带有词法定界(lexical scoping)的第一类值(first-class values)。
**第一类值(first-class values)**指:在 Lua 中函数和其他值(数值、字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值。
**词法定界(lexical scoping)**指:被嵌套的函数可以访问他外部函数中的变量。这一特性给 Lua 提供了强大的编程能力。
Lua 中我们经常这样写:
1 | function foo (x) return 2*x end |
这实际上是利用 Lua 提供的“语法上的甜头”(syntactic sugar)的结果,下面是原本的函数:
1 | -- why you try like that? because i dont't like sugar |
1 | -- 根据学生的成绩从高到低对学生进行排序, 这里的 names, grades 作用域是这个chunk的全局 |
以其他函数作为参数的函数在 Lua 中被称作高级函数
,高级函数在 Lua 中并没有特权,只是 Lua 把函数当作第一类函数
处理的一个简单的结果。
1 | function sortbygrade (names, grades) |
包含在 sortbygrade 函数内部的 sort 中的匿名函数可以访问 sortbygrade 的参数grades,
在匿名函数内部
grades 不是全局变量也不是局部变量,我们称作外部的局部变量
(external local variable)或者 upvalue
。
技术上来讲,闭包
指值而不是指函数,函数仅仅是闭包的一个原型声明
;
闭包的声明周期?
简单的说闭包
是 一个函数加上它可以正确访问的 upvalues。
1 | Lib = {} |
Lua 把 chunk
当作函数
处理,在 chunk 内可以声明局部函数
(仅仅在 chunk 内可见),词法定界保证了包内的其他函数
可以调用此函数。
1 | -- error usage |
上面这种方式导致 Lua 编译时遇到 fact(n-1)并不知道他是局部函数 fact
,Lua 会去查找是否有这样的全局函数 fact
。
为了解决这个问题我们必须在定义函数以前 先声明:
1 | local fact |
note:但是 Lua 扩展了他的语法使得可以在直接递归函数定义时使用两种方式都可以。
note:在定义非
直接递归局部函数时要先声明然后定义才可以
🌳6.3 proper tail calls 尾调用
1 | function f(x) |
这种情况下当被调用函数 g 结束时程序不需要返回到调用者 f
;
所以尾调用之后程序不需要在栈中保留关于调用者的任何信息。
一些编译器比如 Lua 解释器利用这种特性在处理尾调用时不使用额外的栈,我们称这种语言支持正确的尾调用
。
note:由于尾调用不需要使用栈空间,那么尾调用递归的层次可以无限制的。例如下面调用不论 n 为何值不会导致栈溢出。
1 | function foo (n) |
可以将尾调用理解成一种 goto,在状态机的编程领域尾调用是非常有用的。
状态机的应用要求函数记住每一个状态,改变状态只需要 goto(or call)一个特定的函数。
传统模式的编译器对于尾调用的处理方式就像处理其他普通函数调用一样,总会在调用时创建一个新的栈帧(stack frame)并将其推入调用栈顶部,用于表示该次函数调用。
当一个函数调用发生时,计算机必须 “记住” 调用函数的位置 —— 返回位置,才可以在调用结束时带着返回值回到该位置,返回位置一般存在调用栈上。在尾调用这种特殊情形中,计算机理论上可以不需要记住尾调用的位置而从被调用的函数直接带着返回值返回调用函数的返回位置(相当于直接连续返回两次)。
尾调用消除即是在不改变当前调用栈(也不添加新的返回位置)的情况下跳到新函数的一种优化(完全不改变调用栈是不可能的,还是需要校正调用栈上形式参数与局部变量的信息。)
由于当前函数帧上包含局部变量等等大部分的东西都不需要了,当前的函数帧经过适当的更动以后可以直接当作被尾调用的函数的帧使用,然后程序即可以跳到被尾调用的函数。产生这种函数帧更动代码与 “jump”(而不是一般常规函数调用的代码)的过程称作尾调用消除(Tail Call Elimination)或尾调用优化(Tail Call Optimization, TCO)。尾调用优化让位于尾位置的函数调用跟 goto 语句性能一样高,也因此使得高效的结构编程成为现实。
然而,对于 C++ 等语言来说,在函数最后 return g(x); 并不一定是尾递归——在返回之前很可能涉及到对象的析构函数,使得 g(x) 不是最后执行的那个。这可以通过返回值优化来解决。
1 | function foo(data1, data2) |
优化后汇编代码:
1 | foo: |
🌳7. iterator & genericity for 迭代器 泛型for
创建一个闭包
必须要创建其外部局部变量。
所以一个典型的闭包的结构包含两个函数:一个是闭包
自己;另一个是工厂(创建闭包的函数
)。
1 | function list_iter (t) |
note:这个例子中 list_iter 是一个工厂,每次调用他都会创建一个新的闭包(迭代器本身)
1 | -- 迭代器 |
🌳7.2 genericity for 泛型 for 的语义
一般式:
1 | for var_1, ..., var_n in explist do block end |
🌳7.3 iterator without status 无状态的迭代器
note:迭代的状态包括被遍历的表(循环过程中不会改变的状态常量
)和当前的索引下标(控制变量
),
1 | function iter (a, i) |
note:当 Lua 调用 ipairs(a)开始循环时,他获取三个值:迭代函数 iter,状态常量 a 和控制变量初始值 0;然后 Lua 调用 iter(a,0)返回 1,a[1](除非 a[1]=nil);直到第一个非 nil 元素
1 | local n |
🌳7.3 multi status iterator 多状态的迭代器
🌳8. compile run debug 编译 运行 调试
note: 解释型语言的特征不在于他们是否被编译,而是编译器
是语言运行时
的一部分
loadfile:编译代码成中间码并且返回编译后的 chunk 作为一个函数,而不执行代码;
note:在发生错误的情况下,loadfile 返回 nil 和错误信息,这样我们就可以自定义错误处理
1 | f = loadstring("i = i + 1") |
note:f 将是一个函数,调用时执行 i=i+1
Lua 把每一个 chunk 都作为一个匿名函数
处理。
例如:chunk “a = 1”,
loadstring
返回与其等价的 function () a = 1 end
与其他函数一样,
chunks 可以定义局部变量也可以返回值:f = loadstring("local a = 10; return a + 20")
note:loadfile 和 loadstring 都不会抛出错误,如果发生错误他们将返回 nil 加上错误信息:
note:Lua 中的函数定义
是发生在运行时的赋值
而不是发生在编译时。
如果你想快捷的调用 dostring(比如加载并运行),可以这样
loadstring(s)()
如果加载的内容存在语法错误
的话,loadstring 返回 nil 和错误信息(attempt to call a nil value);为了返回更清楚的错误信息可以使用 assert:
assert(loadstring(s))()
note:每次调用 loadstring 都会重新编译,loadstring 编译的时候不关心词法范围
1 | local i = 0 |
note:这个例子中,和想象的一样 g
使用局部变量 i,然而 f
使用全局变量 i;loadstring
总是在全局环境中编译他的串。
note:loadstring 期望一个 chunk,即语句。如果想要加载表达式
,需要在表达式前加 return
,那样将返回表达式的值。
🌳8.1 require
- 会搜索目录加载文件
- 会判断是否文件已经加载避免重复加载同一文件
- ?;?.lua;c:\windows?;/usr/local/lua/?/?.lua
- require 和 dofile 完成同样的功能
1 | require lili |
note:表中保留加载的文件的虚名,而不是实文件名。所以如果你使用不同的虚文件名 require同一个文件两次,将会加载两次该文件。比如 require "foo"和 require “foo.lua”,将会加载 foo.lua 两次,全局变量_LOADED
访问文件名列表, _REQUIREDNAME
🌳8.2 C Packages
note:动态连接库不是 ANSI C 的一部分,也就是说在标准 C 中实现动态连接是很困难的。
1 | local path = "/usr/local/lua/lib/libluasocket.so" |
🌳8.3 error 错误
1 | print "enter a number:" |
🌳8.4 exception & pcall
🌳8.5 exception msg & xpcall
1 | error("string expected", 2) |
🌳9.0 coroutine
协同程序(coroutine)与多线程情况下的线程比较类似:有自己的堆栈,自己的局部变量,有自己的指令指针,但是和其他协同程序共享全局变量等很多信息。
线程和协同程序的主要不同在于:在多处理器情况下,从概念上来讲多线程程序同时运行多个线程;而协同程序是通过协作来完成,在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。
1 | co = coroutine.create(function () print("hi") end) |
note:协同有三个状态:挂起态、运行态、停止态.当我们创建一个协同程序时他开始的状态为挂起态,也就是说我们创建协同程序的时候不会自动运行,
1 | print(coroutine.status(co)) --> suspended |
note:resume 运行在保护模式下,因此如果协同内部存在错误 Lua 并不会抛出错误而是将错误返回给 resume 函数。
1 | -- yield 返回的额外的参数也将会传递给 resume。 |
- resume 把额外的参数传递给协同的主程序。
- resume 返回除了 true 以外的其他部分将作为参数传递给相应的 yield, yield 返回的额外的参数也将会传递给 resume。
- 当协同代码结束时主函数返回的值都会传给相应的 resume
对称协同:由执行到挂起之间状态转换的函数是相同的。
不对称协同(半协同):挂起一个正在执行的协同的函数与使一 个被挂起的协同再次执行的函数是不同的
🌳9.2 filter 过滤器
过滤器:指在生产者与消费者之间,可以对数据 进行某些转换处理。过滤器在同一时间既是生产者又是消费者,他请求生产者生产值并 且转换格式后传给消费者
example:
1 | function receive (prod) |
🌳9.3 iterator 迭代器
origin:
1 | function permgen (a, n) |
example:
1 | function permgen (a, n) |
coroutine.wrap:wrap 创建一个协同程序;不同的是 wrap 不返回协 同本身,而是返回一个函数,当这个函数被调用时将 resume 协同
1 | function perm (a) |
🌳9.4 pre-emptive multi thread 非抢占式多线程
note:协同是非抢占式的。 当一个协同正在运行时,不能在外部终止他。只能通过显示的调用 yield 挂起他的执行。
1 | 协同是非抢占式的。 当一个协同正在运行时,不能在外部终止他。只能通过显示的调用 yield 挂起他的执行。 |
table & object
🌳11. datastruct
🌳11.1 array 数组
🌳11.2 matrix & multidimensional array 阵 多维数组
稀疏矩阵:指矩阵的大部分元素都为空或者 0 的矩阵。
🌳11.3 linked list s链表
🌳11.4 queen & dique s队列 双端队列
note:Lua 的 table 库提供的 insert 和 remove 操作来实现队列,但这种方式 实现的队列针对大数据量时效率太低
note:有效的方式是使用两个索引下标,一个表示第一个元素,另一个表示最后一个元素。
🌳11.5 set & package 集合 包
🌳11.6 string buffer 字符串缓冲
1 | -- WARNING: bad code ahead!! |
假定在 loop 中间,buff 已经是一个 50KB 的字符串, 每一行的大小为 20bytes,
当 Lua 执行 buff..line.."\n"
时,她创建了一个新的字符串大小为 50,020 bytes,并且从 buff 中将 50KB 的字符串拷贝到新串中。
老的字符串变成了垃圾数据,两轮循环之后,将有两个老串包含超过 100KB 的垃圾 数据。这个时候 Lua 会做出正确的决定,进行他的垃圾收集并释放 100KB 的内存。问题 在于每两次循环 Lua 就要进行一次垃圾收集,读取整个文件需要进行 200 次垃圾收集。 并且它的内存使用是整个文件大小的三倍。
note:其它的采用垃圾收集算法的并且字符串不可变的语言 也都存在这个问题。Java 专门提供 StringBuffer 来改善这种情况。
1 | function newStack () |
🌳12. data file storage & Persistence 数据文件与持久化
string.format("%q", o)
🌳13. metatables metamethods
1 | t1 = {} |
note:一组相关的表可以共享一个 metatable (描述他们共同的行为)。一个表也可以是自身的 metatable(描述其私有行为)。
🌳13.1 arithmetic operation metamethods 算术运算的 matamethods
1 | Set = {} |
note:除了__add,__mul, 还有__sub(减),__div(除),__unm(负),__pow(幂),我们也可以定义__concat 定义连接行为。
如果两个操作数有不同的 metatable, Lua 选择 metamethod 的原则:
- 如果第一个参数存在带有__add 域的 metatable,Lua 使用它作为 metamethod,和第二个参数无关;
- 否则第二个参数存在带有__add 域的 metatable,Lua 使用它作为 metamethod 否则报错。
🌳13.2 relational operation metamethods 关系运算的 metamethods
note:__eq(等于),__lt(小于) ,和__le(小于等于)
note:当我们遇到偏序(partial order)情况,也就是说,并不是所有的元素都可以正确的被排序情况。例如,在大多数机器上浮点数不能被排序,因为他的值不是一个数字(Not a Number 即 NaN)
note:根据 IEEE 754 的标准,NaN 表示一个未定义的值,比如 0/0 的结果。该标准指出任何涉及到 NaN 比较的结果都应为 false。也就是说,NaN <= x 总是 false,x < NaN 也总是 false。这样一来,在这种情况下 a <= b 转换为 not (b < a)就不再正确了。
note:<=代表集合的包含:a <= b 表示集合 a 是集合 b 的子集。这种意义下,可能 a <= b 和 b < a 都是 false;
note:关系元算的 metamethods 不支持混合类型运算
note:试图比较一个字符串和一个数字,Lua 将抛出错误.相似的,如果你试图比较两个带有不同 metamethods 的对象,Lua 也将抛出错误。
note:但相等比较从来不会抛出错误,如果两个对象有不同的 metamethod,比较的结果为false,甚至可能不会调用 metamethod.
note:仅当两个有共同的 metamethod 的对象进行相等比较的时候,Lua 才会调用对应的 metamethod。
🌳13.3 others metamethods
note:print
函数总是调用 tostring 来格式化它的输出, tostring 会首先检查对象是否存在一个带有__tostring
域的 metatable。
假定你想保护你的集合使其使用者既看不到也不能修改 metatables。
如果你对 metatable 设置了__metatable 的值, getmetatable 将返回这个域的值,而调用 setmetatable将会出错:
Set.mt.__metatable = “not your business”
🌳13.4 table relative metamethods 表相关 metamethods
🌳13.4.1 The __index Metamethod
note:当我们访问一个表的不存在的域,这种访问触发 lua 解释器去查找__index metamethod
note:__index metamethod 不需要非是一个函数,他也可以是一个表。
- 它是一个函数的时候,Lua 将 table 和缺少的域作为参数调用这个函数;
- 他是一个表的时候,Lua 将在这个表中看是否有缺少的域。
🌳13.4.2 The __newindex Metamethod
note:当你给表的一个缺少的域赋值,解释器就会查找__newindex metamethod,如果存在则调用这个函数而不进行赋值操作。
调用rawset(t,k,v)不掉用任何 metamethod 对表 t 的 k 域赋值为 v。
🌳13.4.3 table default value 有默认值的表
🌳13.4.4 table monitor 监控表
单表监控
1 | t = {} -- original table (created somewhere) |
note:注意:不幸的是,这个设计不允许我们遍历表。
多表监控
1 | -- create private index |
🌳13.4.5 readonly table 只读表
1 | function readOnly (t) |
🌳14. environment 环境
🌳14.1 dynamic access global value 使用动态名字访问全局变量
1 | function getfield (f) |
🌳14.2 delcare global value 声明全局变量
1 | local declaredNames = {} |
🌳14.3 un-global environment 非全局的环境
note:当你安装一个 metatable 去控制全局访问时,你的整个程序都必须遵循同一个指导方针。如果你想使用标准库,标准库中可能使用到没有声明的全局变量,你将碰到坏运。
Setfenv:接受函数和新的环境作为参数。除了使用函数本身,还可以指定一个数字表示栈顶的活动函数。数字 1 代表当前函数,数字 2 代表调用当前函数的函数
1 | a = 1 -- create a global variable |
必须在单独的 chunk 内运行这段代码,如果你在交互模式逐行运行他,每一行都是一个不同的函数,调用 setfenv 只会影响他自己的那一行
封装: populate
1 | a = 1 -- create a global variable |
继承封装
1 | a = 1 -- create a global variable |
note:当你创建一个新的函数时,他从创建他的函数继承了环境变量
note:如果一个chunk 改变了他自己的环境,这个 chunk 所有在改变之后定义的函数都共享相同的环境,都会受到影响。这对创建命名空间是非常有用的机制
🌳15 package
note:大多数语言中,packages 不是第一类值(first-class values)(也就是说,他们不能存储在变量里,不能作为函数参数。。。 )
- 对每一个函数定义都必须显示的在前面加上包的名称。
- 同一包内的函数
相互调用
必须在被调用函数前指定包名。
缺点:
- 修改函数的状态(公有变成私有或者私有变成公有)我们必须修改函数得调用方式。
- 访问同一个package 内的其他公有的实体写法冗余,必须加上前缀 P.。
1 | local function checkComplex (c) |
🌳15.3 package & file
note:*当 require 加载一个文件的时候,它定义了一个变量来表示虚拟的文件名*
1
2
3
4
5
6
7
-- 不需要 require 就可以使用 package
local P = {} -- package
if _REQUIREDNAME == nil then
complex = P
else
_G[_REQUIREDNAME] = P
end
note:*我们可以在同一个文件之内定义多个 packages,我们需要做的只是将每一个 package 放在一个 **do 代码块***内,这样 local 变量才能被限制在那个代码块中*
自动加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
local location = {
foo = "/usr/local/lua/lib/pack1_1.lua",
goo = "/usr/local/lua/lib/pack1_1.lua",
foo1 = "/usr/local/lua/lib/pack1_2.lua",
goo1 = "/usr/local/lua/lib/pack1_3.lua",
}
pack1 = {}
setmetatable(pack1, {__index = function (t, funcname)
local file = location[funcname]
if not file then
error("package pack1 does not define " .. funcname)
end
assert(loadfile(file))() -- load and run definition
return t[funcname] -- return the function
end})
return pack1
🌳16 object-oriented programming 面向对象程序设计
-
示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25Account = {
balance=0,
withdraw = function (self, v)
self.balance = self.balance - v
end
}
function Account:deposit (v)
self.balance = self.balance + v
end
Account.deposit(Account, 200.00)
Account:withdraw(100.00)
function Account:new (o)
o = o or {} -- create object if user does not provide one
setmetatable(o, self)
self.__index = self
return o
end
a = Account:new{balance = 0}
a:deposit(100.00)
getmetatable(a).__index.deposit(a, 100.00)
Account.deposit(a, 100.00)🌳16.3 multiple inheritance 多重继承
多重继承意味着一个类拥有多个父类
🌳16.4 private 私有性
通过闭包实现私有性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36function newAccount (initialBalance)
local self = {
balance = initialBalance,
LIM = 10000.00,
}
local withdraw = function (v)
self.balance = self.balance - v
end
local deposit = function (v)
self.balance = self.balance + v
end
local extra = function ()
if self.balance > self.LIM then
return self.balance*0.10
else
return 0 end
end
local getBalance = function ()
return self.balance + self.extra()
end
--local getBalance = function () return self.balance end
return {
withdraw = withdraw,
deposit = deposit,
getBalance = getBalance
}
end
acc1 = newAccount(100.00)
acc1.withdraw(40.00)
print(acc1.getBalance()) --> 60🌳16.5 Single-Method 的对象实现方法
1
2
3
4
5
6
7
8
9
10
11
12
13function newObject (value)
return function (action, v)
if action == "get" then return value
elseif action == "set" then value = v
else error("invalid action")
end
end
end
d = newObject(0)
print(d("get")) --> 0
d("set", 10)
print(d("get")) --> 10
🌳17 weak table
一个 weak 引用是指一个不被 Lua 认为是垃圾的对象的引用。
如果一个对象所有的引用指向都是weak,对象将被收集,而那些 weak 引用将会被删除。
如果一个对象只存在于 weak tables 中,Lua 将会最终将它收集。
三种类型的 weak tables:
-
weak keys 组成的 tables;
-
weak values 组成的 tables;
-
以及纯 weak tables 类型,他们的 keys 和 values 都是 weak 的。
表的 weak 性由他的 metatable 的__mode 域来指定的。
1
2
3
4
5
6
7
8
9
10a = {}
b = {}
setmetatable(a, b)
b.__mode = "k" -- now 'a' has weak keys
key = {} -- creates first key
a[{key}] = 1
key = {} -- creates second key
a[key] = 2
collectgarbage() -- forces a garbage collection cycle
for k, v in pairs(a) do print(v) end第二个赋值语句 key={}覆盖了第一个 key 的值。当垃圾收集器工作时,在其他地方没有指向第一个 key 的引用,所以它被收集了
note:只有对象才可以从一个 weak table 中被收集。比如数字和布尔值类型的值,都是不会被收集的。
example:如果我们在 table 中插入了一个数值型的 key(在前面那个例子中),它将永远不会被收集器从 table 中移除。当然,如果对应于这个数值型 key 的 vaule被收集,那么它的整个入口将会从 weak table 中被移除。
note:一个字符串不会从 weak tables 中被移除(除非它所关联的 vaule 被收集)
🌳17.1 记忆函数
如果这个结果表中有 weak 值,每次的垃圾收集循环都会移除当前时间内所有未被使用的结果
1
2
3
4
5
6
7
8
9
10
11local results = {}
setmetatable(results, {__mode = "v"}) -- make values weak
function createRGB (r, g, b)
local key = r .. "-" .. g .. "-" .. b
if results[key] then return results[key]
else
local newcolor = {red = r, green = g, blue = b}
results[key] = newcolor
return newcolor
end
end
标准库
🌳18. 数学库
三角函数库(sin, cos, tan, asin, acos, etc.)
幂指函数(exp, log, log10),
舍入函数(floor, ceil)、
max、min,加上一个变量 pi。
所有的三角函数都在弧度单位下工作。(Lua4.0 以前在度数下工作。)
你可以使用 deg
和 rad
函数在度和弧度之间转换。
math.random 用来产生伪随机数,有三种调用方式:
- 第一:不带参数,将产生 [0,1)范围内的随机数.
- 第二:带一个参数 n,将产生 1 <= x <= n 范围内的随机数 x.
- 第三:带两个参数 a 和 b,将产生 a <= x <= b 范围内的随机数 x.
1 | math.randomseed(os.time()) |
🌳19. table lib
table.getn table.setn 已经弃用
1 | a = {nil, 1, 2, key = '1234321', nil, 15, 'sagewqgag', nil} |
ipairs iterate number index until first nil
pairs iterate all except nil
when {nil, 1, 2, key = '1234321', nil, 15, 'sagewqgag', nil}
, #a = 6
when {1, 2, key = '1234321', nil, 15, 'sagewqgag', nil}
, #a = 2
when {1, 2, key = '1234321', 15, 'sagewqgag', nil}
, #a = 4
when {1, 2, key = '1234321', 15, 'sagewqgag'}
, #a = 4
table.insert 函数在 array指定位置插入一个元素,并将后面所有其他的元素后移。
不带位置参数调用 insert,将会在 array最后位置插入元素(所以不需要元素移动)
note:排序函数有两个参数并且如果在 array 中排序后第一个参数在第二个参数前面,排序函数必须返回 true。如果未提供排序函数,sort 使用默认的小于操作符进行比较
🌳20 string lib
string.len(s)
返回字符串 s 的长度;
string.rep(s,n)
返回重复 n 次字符串 s 的串;
你使用 string.rep("a", 2^20)
可以创建一个 1M bytes 的字符串(比如,为了测试需要) ;
string.lower(s)
将 s 中的大写字母转换成小写(string.upper
将小写转换成大写)
string.sub(s,i,j)
函数截取字符串 s 的从第 i 个字符到第 j 个字符之间的串。
Lua中,字符串的第一个字符索引从 1 开始。你也可以使用负索引,负索引从字符串的结尾向前计数:-1 指向最后一个字符,-2 指向倒数第二个,以此类推。
note:Lua 中的字符串是恒定不变的。String.sub 函数以及 Lua 中其他的字符串操作函数都不会改变字符串的值,而是返回一个新的字符串。
如果你想修改一个字符串变量的值,你必须将变量赋给一个新的字符串:
1 | s = string.sub(s, 2, -2) |
🌳20.1 pattern matching 模式匹配
Lua并不使用POSIX规范的正则表达式(也写作regexp)来进行模式匹配。
1 | s = "hello world" |
note:_ 只是一个哑元变量
🌳20.2 pattern 模式
+ 匹配前一字符 1 次或多次
* 匹配前一字符 0 次或多次
- 匹配前一字符 0 次或多次
? 匹配前一字符 0 次或 1 次
'[_%a][_%w]*' 匹配 Lua 程序中的标示符:字母或者下划线开头的字母下划线数字序列。
匹配一对圆括号()或者括号之间的空白,可以使用 '%(%s*%)'
1 | s = "Deadline is 30/05/1999, firm" |
🌳20.3 matchings 捕获
1 | pair = "name = Anna" |
🌳21. IO lib
🌳21.1 simple I/O mode
io.input 和 io.output 函数来改变当前文件。
例如io.input(filename)就是打开给定文件(以读模式),并将其设置为当前输入文件。
接下来所有的输入都来自于该文,直到再次使用 io.input。io.output 函数。
一旦产生错误两个函数都会产生错误。
1 | > io.write("sin (3) = ", math.sin(3), "\n") |
在编写代码时应当避免像 io.write(a…b…c);这样的书写,这同 io.write(a,b,c)的效果是一样的。但是后者因为避免了串联操作,而消耗较少的资源。
Write 函数与 print 函数不同在于,write 不附加任何额外的字符到输出中去,例如制表符,换行符等等。
write 函数是使用当前输出文件,而 print 始终使用标准输出。
print函数会自动调用参数的 tostring方法,所以可以显示出表(tables)函数(functions)和 nil。
“*all” 读取整个文件
“*line” 读取下一行
“*number” 从串中转换出一个数值
num 读取 num 个字符到串
io.read("*all")函数从当前位置读取整个输入文件。如果当前位置在文件末尾,或者文件为空,函数将返回空串。
io.read("*line")函数返回当前输入文件的下一行(不包含最后的换行符)。当到达文件末尾,返回值为 nil(表示没有下一行可返回)
该读取方式是 read 函数的默认方式,所以可以简写为 io.read()。
io.read("*number")函数从当前输入文件中读取出一个数值。只有在该参数下 read 函数才返回数值,而不是字符串。
如果在当前位置找不到一个数字(由于格式不对,或者是到了文件的结尾),则返回 nil 可以对每个参数设置选项,函数将返回各自的结果。
1 | --[[ |
🌳21.2 complete I/O mode
1 | local f = assert(io.open(filename, "r")) |
I/O 优化的一个小技巧:
通常 Lua 中读取整个文件要比一行一行的读取一个文件快的多。
1 | local lines, rest = f:read(BUFSIZE, "*line") |
🌳22 system lib 操作系统库
Lua 是以 ANSI C 写成的
1 | -- obs: 10800 = 3*60*60 (3 hours) |
🌳23. debug lib
debug 库由两种函数组成:自省(introspective
)函数和 hooks
。
自省函数 使得我们可以检查运行程序的某些方面,比如活动函数栈、当前执行代码的行号、本地变量的名和值。
Hooks 可以跟踪程序的执行情况。
1 | debug.getinfo(foo) |
以数字 n 调用 debug.getinfo(n)
时,返回在 n 级栈的活动函数的信息数据。
比如,如果 n=1,返回的是正在进行调用的那个函数的信息。(n=0 表示 C 函数 getinfo 本身)
如果 n 比栈中活动函数的个数大的话,debug.getinfo 返回 nil。
1 | function traceback () |
🌳23.2 hooks
Lua 使用单个参数调用 hooks,参数为一个描述产生调用的事件:“call”、“return”、“line” 或 “count”。
有四种可以触发一个 hook 的事件:
- 当 Lua 调用一个函数的时候 call 事件发生;
- 每次函数返回的时候,return 事件发生;
- Lua 开始执行代码的新行时候,line 事件发生;
- 运行指定数目的指令之后,count 事件发生。
C API
🌳24. C API Overview
Lua 解释器是一个使用 Lua 标准库实现的独立的解释器,她是一个很小的应用(总共不超过 500 行的代码)。
解释器负责程序和使用者的接口:从使用者那里获取文件或者字符串,并传给 Lua 标准库,Lua 标准库负责最终的代码运行。
第一种,C 作为应用程序语言,Lua 作为一个库使用;
第二种,反过来,Lua 作为程序语言,C 作为库使用。
C API:
- 读写 Lua 全局变量的函数,
- 调用 Lua 函数的函数,
- 运行 Lua 代码片断的函数,
- 注册 C 函数然后可以在Lua 中被调用的函数,等等。
压栈函数:
- 空值(nil)用 lua_pushnil,
- 数值型(double)用 lua_pushnumber,
- 布尔型(在 C 中用整数表示)用lua_pushboolean,
- 任意的字符串(char*类型,**允许包含'\0'字符**)用 lua_pushlstring,C语言风格(以'\0'结束)的字符串(const char*)用 lua_pushstring:
Lua 中的字符串不是以零为结束符的;它们依赖于一个明确的长度,因此可以包含任意的二进制数据。
Lua 从来不保持一个指向外部字符串(或任何其它对象,除了 C 函数——它总是静态指针)的指针。
1 | int lua_checkstack (lua_State *L, int sz); |
lua_isnumber 和 lua_isstring 函数不检查这个值是否是指定的类型,而是看它是否能被转换成指定的那种类型。例如,任何数字类型都满足 lua_isstring
lua_type 函数,它返回栈中元素的类型。
给定的元素的类型不正确,lua_toboolean、lua_tonumber 和 lua_strlen 返回 0,其他函数返回 NULL
当一个 C 函数返回后,Lua 会清理他的栈
1 | const char *s = lua_tostring(L, -1); /* any Lua string */ |
函数 lua_gettop 返回堆栈中的元素个数,它也是栈顶元素的索引
一个负数索引-x 对应于正数索引 gettop-x+1。
lua_settop 设置栈顶(也就是堆栈中的元素个数)为一个指定的值。
- 如果开始的栈顶高于新的栈顶,顶部的值被丢弃。
- 否则,为了得到指定的大小这个函数压入相应个数的空值(nil)到栈上。
lua_settop(L,0)清空堆栈。
函数 lua_pushvalue 压入堆栈上指定索引的一个抟贝到栈顶;
lua_remove 移除指定索引位置的元素,并将其上面所有的元素下移来填补这个位置的空白;
lua_insert 移动栈顶元素到指定索引的位置,并将这个索引位置上面的元素全部上移至栈顶被移动留下的空隔;
最后,lua_replace 从栈顶弹出元素值并将其设置到指定索引位置,没有任何移动操作。
1 | static void stackDump (lua_State *L) { |
🌳24.3 C API exception handler
当我们写一个库代码时(也就是被 Lua 调用的 C 函数)长跳转(long jump)的用处几乎和一个真正的异常处理一样的方便,因为 Lua 抓取了任务偶然的错误。
panic 函数
lua_pcall
lua_cpcall
luaL_error
🌳25. invoking lua function
🌳26. invoking C function
1 | lua_pushcfunction(l, l_sin); |
🌳28. user-defined types in C
接口访问形式:
1 | a = array.new(1000) |
1 |
|
note:luaL_openlib 的另一个特征,第一次调用,当我们传递一个 NULL作为库名时,luaL_openlib 并没有创建任何包含函数的表;相反,他认为封装函数的表在栈内,位于临时的 upvalues 的下面。
在这个例子中,封装函数的表是 metatable 本身,也就是 luaL_openlib 放置方法的地方。*
面向对象访问形式:
1 | a = array.new(1000) |
🌳29. resource managerment 资源管理
1 |
|
🌳Lua 版本更新记录
🌳中文版
🌳自 Lua 5.3 以来的变化
以下是 Lua 5.4 引入的主要变化。参考手册列出了必须引入的不兼容性。
🌳主要变化
- 垃圾收集的新生代模式(新方法 意味着更频繁地启动较短的跟踪,仅涵盖最近创建的对象。 仅在短暂的爬网之后无法达到所需的内存消耗指标时,才执行所有对象的完全爬网。 这种方法可实现更高的性能和更低的内存消耗 在存储大量存在时间短的对象的情况下。)
- 待关闭变量
- 常量变量
- userdata 可以有多个用户值
- math.random 的新实现
- 警告系统
- 关于函数参数和返回值的调试信息
- 整数 ‘for’ 循环的新语义
- 可选的 ‘init’ 参数用于 ‘string.gmatch’
- 新函数 ‘lua_resetthread’ 和 ‘coroutine.close’
- 字符串到数字的强制转换移至字符串库
- 当缩小内存块时,允许分配函数失败
- ‘string.format’ 中的新格式 ‘%p’
- utf8 库接受最多 2^31 的代码点
🌳自 Lua 5.2 以来的变化
以下是 Lua 5.3 引入的主要变化。参考手册列出了必须引入的不兼容性。
🌳主要变化
- 整数(默认 64 位)
- 官方支持 32 位数字
- 位运算符
- 基本的 utf-8 支持
- 用于打包和解包值的函数
- 以下是 Lua 5.3 引入的其他变化:
🌳语言
- userdata 可以有任何 Lua 值作为用户值
- 向下取整除法
- 一些元方法的更灵活规则
🌳库
- ipairs 和 table 库尊重元方法
- string.dump 中的 strip 选项
- table 库尊重元方法
- 新函数 table.move
- 新函数 string.pack
- 新函数 string.unpack
- 新函数 string.packsize
🌳C API
- 更简单的 C 中的延续函数 API
- lua_gettable 和类似函数返回结果值的类型
- lua_dump 中的 strip 选项
- 新函数:lua_geti
- 新函数:lua_seti
- 新函数:lua_isyieldable
- 新函数:lua_numbertointeger
- 新函数:lua_rotate
- 新函数:lua_stringtonumber
🌳Lua 独立解释器
- 可用作计算器;无需前缀 ‘=’
- arg 表对所有代码可用
🌳自 Lua 5.1 以来的变化
以下是 Lua 5.2 引入的主要变化。参考手册列出了必须引入的不兼容性。
🌳主要变化
- 可挂起的 pcall 和元方法
- 全局变量的新词法方案
- 短命表
- 位运算的新库
- 轻量级 C 函数
- 紧急垃圾收集器
- goto 语句
- 表的终结器
以下是 Lua 5.2 引入的其他变化:
🌳语言
- 线程或函数不再有 fenv
- 表尊重 __len 元方法
- 字符串中的十六进制和 \z 转义
- 支持十六进制浮点数
- 不同类型的顺序元方法
- 不再验证操作码一致性
- 钩子事件 “tail return” 被 “tail call” 取代
- 空语句
- break 语句可以出现在块的中间
🌳库
- 通过 xpcall 调用的函数的参数
- load 和 loadfile 的可选 ‘mode’ 参数(控制二进制 x 文本)
- load 和 loadfile 的可选 ‘env’ 参数(加载块的环境)
- loadlib 可以加载具有全局名称的库(RTLD_GLOBAL)
- 新函数 package.searchpath
- 模块加载时接收它们的路径
- math.log 中的可选基数
- string.rep 中的可选分隔符
- file:write 返回文件
- 关闭管道返回退出状态
- os.exit 可以关闭状态
- 新元方法 __pairs 和 __ipairs
- collectgarbage 和 lua_gc 的新选项 ‘isrunning’
- 边界模式
- 模式中的 \0
- io.read 的新选项 *L
- io.lines 的选项
- debug.getlocal 可以访问函数的可变参数
🌳C API
- 注册表中预定义的主线程
- 新函数 lua_absindex, lua_arith, lua_compare, lua_copy, lua_len, lua_rawgetp, lua_rawsetp, lua_upvalueid, lua_upvaluejoin, lua_version.
- 新函数 luaL_checkversion, luaL_setmetatable, luaL_testudata, luaL_tolstring.
- lua_pushstring 和 pushlstring 返回字符串
- debug API 中可用的 nparams 和 isvararg
- 新的 lua_Unsigned
🌳实现
- 每个函数的最大常量提高到 226
- 垃圾收集的代际模式(实验性)
- NaN 技巧(实验性)
- ctypes 的内部(不可变)版本
- 字符串缓冲区的更简单实现
- 解析器使用更少的 C 栈空间(不再有自动数组)
🌳Lua 独立解释器
- 新的 -E 选项以避免环境变量
- 处理非字符串错误消息
🌳英文原版
🌳Changes since Lua 5.3
Here are the main changes introduced in Lua 5.4. The reference manual lists the incompatibilities that had to be introduced.
🌳Main changes
- new generational mode for garbage collection
- to-be-closed variables
- const variables
- userdata can have multiple user values
- new implementation for math.random
- warning system
- debug information about function arguments and returns
- new semantics for the integer ‘for’ loop
- optional ‘init’ argument to ‘string.gmatch’
- new functions ‘lua_resetthread’ and ‘coroutine.close’
- string-to-number coercions moved to the string library
- allocation function allowed to fail when shrinking a memory block
- new format ‘%p’ in ‘string.format’
- utf8 library accepts codepoints up to 2^31
🌳Changes since Lua 5.2
Here are the main changes introduced in Lua 5.3. The reference manual lists the incompatibilities that had to be introduced.
🌳Main changes
- integers (64-bit by default)
- official support for 32-bit numbers
- bitwise operators
- basic utf-8 support
- functions for packing and unpacking values
- Here are the other changes introduced in Lua 5.3:
🌳Language
- userdata can have any Lua value as uservalue
- floor division
- more flexible rules for some metamethods
🌳Libraries
- ipairs and the table library respect metamethods
- strip option in string.dump
- table library respects metamethods
- new function table.move
- new function string.pack
- new function string.unpack
- new function string.packsize
🌳C API
- simpler API for continuation functions in C
- lua_gettable and similar functions return type of resulted value
- strip option in lua_dump
- new function: lua_geti
- new function: lua_seti
- new function: lua_isyieldable
- new function: lua_numbertointeger
- new function: lua_rotate
- new function: lua_stringtonumber
🌳Lua standalone interpreter
- can be used as calculator; no need to prefix with ‘=’
- arg table available to all code
🌳Changes since Lua 5.1
Here are the main changes introduced in Lua 5.2. The reference manual lists the incompatibilities that had to be introduced.
🌳Main changes
- yieldable pcall and metamethods
- new lexical scheme for globals
- ephemeron tables
- new library for bitwise operations
- light C functions
- emergency garbage collector
- goto statement
- finalizers for tables
Here are the other changes introduced in Lua 5.2:
🌳Language
- no more fenv for threads or functions
- tables honor the __len metamethod
- hex and \z escapes in strings
- support for hexadecimal floats
- order metamethods work for different types
- no more verification of opcode consistency
- hook event “tail return” replaced by “tail call”
- empty statement
- break statement may appear in the middle of a block
🌳Libraries
- arguments for function called through xpcall
- optional ‘mode’ argument to load and loadfile (to control binary x text)
- optional ‘env’ argument to load and loadfile (environment for loaded chunk)
- loadlib may load libraries with global names (RTLD_GLOBAL)
- new function package.searchpath
- modules receive their paths when loaded
- optional base in math.log
- optional separator in string.rep
- file:write returns file
- closing a pipe returns exit status
- os.exit may close state
- new metamethods __pairs and __ipairs
- new option ‘isrunning’ for collectgarbage and lua_gc
- frontier patterns
- \0 in patterns
- new option *L for io.read
- options for io.lines
- debug.getlocal can access function varargs
🌳C API
- main thread predefined in the registry
- new functions lua_absindex, lua_arith, lua_compare, lua_copy, lua_len, lua_rawgetp, lua_rawsetp, lua_upvalueid, lua_upvaluejoin, lua_version.
- new functions luaL_checkversion, luaL_setmetatable, luaL_testudata, luaL_tolstring.
- lua_pushstring and pushlstring return string
- nparams and isvararg available in debug API
- new lua_Unsigned
🌳Implementation
- max constants per function raised to 226
- generational mode for garbage collection (experimental)
- NaN trick (experimental)
- internal (immutable) version of ctypes
- simpler implementation for string buffers
- parser uses much less C-stack space (no more auto arrays)
🌳Lua standalone interpreter
- new -E option to avoid environment variables
- handling of non-string error messages
🌳Lua 5.4 版本更新
Lua5.4已经进入rc(Release Candidate)状态,相信很快就会发布正式版。这个版本在语言层面上修改的东西并不多,但是默认的GC被换成了“分代式GC”,这对于那些经常产生短期对象的程序应该会有很明显的性能提升。GC带来的负担永远是自动内存管理语言的一大痛点,如果能在这一点上取得突破,那肯定比提供更多语法糖来得有价值。
此外5.4可以指定局部变量的属性,用这样的语法:
local a
NAME可以是const或close,为const时表示const变量(const variables),const变量可以帮助编译器作一些优化,比如下面的代码:
local a
local b = a + 7
print(b)
编译器会把a消除掉,直接给b赋11。这种优化是有限的,对于基本类型和字符串,能够有效减少寄存器的访问,但对于table貌似益处不大。代码文件如果需要一些数值常量,可以写成const变量,比如:
local MAX_LEN
function check_name(name)
return #name <= MAX_LEN
end
在check_name中就没有upvalue的访问,而是直接转换成和20的比较。
close变量(To-be-closed Variables)需要和close元方法结合使用,在变量超出作用域时,会调用变量的close元方法,这听起来是不是有点像C++的RAII用法。下面是一个例子:
local function newlock()
local lock = {
acquire = function()
print(“acquire lock”)
end,
release = function()
print(“release lock”)
end,
}
return lock
end
local function lockguard(lock)
local wrap = {
lock = lock
}
lock.acquire()
return setmetatable(wrap, {__close = function(t, err)
t.lock.release()
end})
end
local lock = newlock()
do
for i = 1, 3 do
local l
print(i)
error(“err”)
end
end
定义local l
除了上面提到的特性,还有一些新的修改罗列如下:
userdata现在可以关联多个user值,C的API也有相应的修改,如果我们新建的userdata没有关联值,则尽量使用lua_newuserdatauv,这样更高效,lua_newuserdata仅仅为了兼容,且默认会关联1个值。
math.random使用了新的算法。
协程库提供了新的APIcoroutine.close和lua_resetthread,coroutine.close只能在挂起或死亡状态下调用,挂起状态下会使用协程进入死亡状态,并且关闭所有的close变量。