lua学习笔记
Lua学习笔记
为什么要学习lua
最重要的当然是工作原因,最近有个项目是相关于游戏服务器的,而用的框架是skynet,用的语言是lua。
然后lua对nginx和redis也有用处,学了也不吃亏,就写这篇东西做一个总结。
看的文档是这个
云风大神写的。
简介:
- lua是一门拓展式程序设计语言。
- 作为一门扩展式语言,Lua 没有 "main" 程序的概念: 它只能 嵌入 一个宿主程序中工作, 该宿主程序被称为 被嵌入程序 或者简称 宿主 。
基本概念
值与类型
- Lua 是一门动态类型语言。 这意味着变量没有类型;只有值才有类型。 语言中不设类型定义。 所有的值携带自己的类型。
- Lua 中所有的值都是 一等公民。 这意味着所有的值均可保存在变量中、 当作参数传递给其它函数、以及作为返回值。
- Lua 中有八种基本类型: nil、boolean、number、string、function、userdata、 thread 和 table。
- Lua 对 8 位是友好的: 字符串可以容纳任意 8 位值, 其中包含零 ('
\0
') 。 Lua 的字符串与编码无关; 它不关心字符串中具体内容。 - userdata 类型允许将 C 中的数据保存在 Lua 变量中。
- thread 类型表示了一个独立的执行序列,被用于实现协程
- table 是一个关联数组, 也就是说,这个数组不仅仅以数字做索引,除了 nil 和 NaN 之外的所有 Lua 值 都可以做索引。
- 一个可以完全表示为整数的浮点数和对应的整数相等 (例如:
1.0 == 1
)。 为了消除歧义,当一个可以完全表示为整数的浮点数做为键值时, 都会被转换为对应的整数储存。 例如,当你写a[2.0] = true
时, 实际被插入表中的键是整数2
。 (另一方面,2 与 "2
" 是两个不同的 Lua 值, 故而它们可以是同一张表中的不同项。)
环境和局部变量
- 每个被编译的 Lua 代码块都会有一个外部的局部变量叫
_ENV
,被_ENV
用于值的那张表被称为 环境。 - Lua 保有一个被称为 全局环境 特别环境。它被保存在 C 注册表 的一个特别索引下。 在 Lua 中,全局变量被初始化为这个值。 ( 不被内部任何地方使用。)
错误处理
- 由于 Lua 是一门嵌入式扩展语言,其所有行为均源于宿主程序中 C 代码对某个 Lua 库函数的调用。 所以,在编译或运行 Lua 代码块的过程中,无论何时发生错误, 控制权都返回给宿主,由宿主负责采取恰当的措施(比如打印错误消息)。
元表及元方法
- Lua 中的每个值都可以有一个 元表。 这个 元表 就是一个普通的 Lua 表, 它用于定义原始值在特定操作下的行为。
- 利用元表可以修改值的默认行为。
垃圾收集
- Lua 采用了自动内存管理。 Lua 运行了一个 垃圾收集器 来收集所有 死对象 (即在 Lua 中不可能再访问到的对象)来完成自动内存管理的工作。 Lua 中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。
- Lua 实现了一个增量标记-扫描收集器。
- 可以修改垃圾收集元方法
__gc
来处理一些额外的资源管理工作。
协程
- Lua 支持协程,也叫 协同式多线程。 一个协程在 Lua 中代表了一段独立的执行线程。但是和go有区别,就是当要让出资源的时候需要调用一个让出(yield)函数时才挂起当前的执行。
- 调用函数 coroutine.create 可创建一个协程。 调用 coroutine.resume 函数执行一个协程,该函数的第一个方法是coroutine创建的对象,其它参数会被当成这个对象的参数。
- 协程的运行可能被两种方式终止: 正常途径是主函数返回 (显式返回或运行完最后一条指令); 非正常途径是发生了一个未被捕获的错误。
- 通过调用 coroutine.yield 使协程暂停执行,让出执行权。 协程让出时,对应的最近 coroutine.resume 函数会立刻返回。 在协程让出的情况下, coroutine.resume 也会返回 true, 并加上传给 coroutine.yield 的参数。 当下次重启同一个协程时, 协程会接着从让出点继续执行。此时,此前让出点处对 coroutine.yield 的调用 会返回,返回值为传给 coroutine.resume 的第一个参数之外的其他参数。
语言定义
词法约定
- Lua 中的 名字 (也被称为 标识符) 可以是由非数字打头的任意字母下划线和数字构成的字符串。
- Lua语言大小写敏感。
-
字面串 可以用单引号或双引号括起。在反斜杠后跟一个真正的换行等价于在字符串中写一个换行符。 转义串 '
\z
' 会忽略其后的一系列空白符,包括换行; 它在你需要对一个很长的字符串常量断行为多行并希望在每个新行保持缩进时非常有用。 - 字面串还可以用一种 长括号 括起来的方式定义。 我们把两个正的方括号间插入 n 个等号定义为 第 n 级开长括号。 就是说,0 级开的长括号写作
[[
, 一级开长括号写作[=[
, 如此等等。 这种方式描述的字符串可以包含任何东西。
a = [[alo
123"]]
a = [==[
alo
123"]==]
变量
-
Lua 中有三种变量: 全局变量、局部变量和表的域。
-
所有没有显式声明为局部变量的变量名都被当做全局变量。
-
var.Name
这种语法只是一个语法糖,用来表示var["Name"]
:var ::= prefixexp ‘.’ Name
-
对全局变量
x
的操作等价于操作_ENV.x
。 -
在变量的首次赋值之前,变量的值均为 nil。
语句
- Lua 把一个代码块当作一个拥有不定参数的匿名函数 来处理。 正是这样,代码块内可以定义局部变量,它可以接收参数,返回若干值。 此外,这个匿名函数在编译时还为它的作用域绑定了一个外部局部变量 _ENV 。 该函数总是把 _ENV 作为它唯一的一个上值, 即使这个函数不使用这个变量,它也存在。
- Lua 允许多重赋值。
控制结构
if, while, and repeat 这些控制结构符合通常的意义,而且也有类似的语法:
stat ::= while exp do block end
stat ::= repeat block until exp
stat ::= if exp then block {elseif exp then block} [else block] end
还有goto,break,return。
for有两种形式:一种是数字形式,另一种是通用形式。
数字形式的 for 循环,通过一个数学运算不断地运行内部的代码块。 下面是它的语法:
stat ::= for Name ‘=’ exp ‘,’ exp [‘,’ exp] do block end
通用形式的 for 通过一个叫作 迭代器 的函数工作。 每次迭代,迭代器函数都会被调用以产生一个新的值, 当这个值为 nil 时,循环停止。 通用形式的 for 循环的语法如下:
stat ::= for namelist in explist do block end
namelist ::= Name {‘,’ Name}
表达式
Lua 中有这些基本表达式:
exp ::= prefixexp
exp ::= nil | false | true
exp ::= Numeral
exp ::= LiteralString
exp ::= functiondef
exp ::= tableconstructor
exp ::= ‘...’
exp ::= exp binop exp
exp ::= unop exp
prefixexp ::= var | functioncall | ‘(’ exp ‘)’
函数调用和可变参数表达式都可以放在多重返回值中。值的个数不一致时,通过补nil和抛弃做调整。需要注意的是被括号括起来的表达式永远被当作一个值。 所以, (f(x,y,z))
即使 f
返回多个值, 这个表达式永远是一个单一值。 ((f(x,y,z))
的值是 f
返回的第一个值。 如果 f
不返回值的话,那么它的值就是 nil 。)
这里有一些例子:
f() -- 调整为 0 个结果
g(f(), x) -- f() 会被调整为一个结果
g(x, f()) -- g 收到 x 以及 f() 返回的所有结果
a,b,c = f(), x -- f() 被调整为 1 个结果 (c 收到 nil)
a,b = ... -- a 收到可变参数列表的第一个参数,
-- b 收到第二个参数(如果可变参数列表中
-- 没有实际的参数,a 和 b 都会收到 nil)
a,b,c = x, f() -- f() 被调整为 2 个结果
a,b,c = f() -- f() 被调整为 3 个结果
return f() -- 返回 f() 的所有返回结果
return ... -- 返回从可变参数列表中接收到的所有参数parameters
return x,y,f() -- 返回 x, y, 以及 f() 的所有返回值
{f()} -- 用 f() 的所有返回值创建一个列表
{...} -- 用可变参数中的所有值创建一个列表
{f(), nil} -- f() 被调整为一个结果
操作符
Lua 支持下列数学运算操作符:
-
+
: 加法 -
-
: 减法 -
\*
: 乘法 -
/
: 浮点除法 -
//
: 向下取整除法 -
%
: 取模 -
^
: 乘方 -
-
: 取负
除了乘方和浮点除法运算, 数学运算按如下方式工作: 如果两个操作数都是整数, 该操作以整数方式操作且结果也将是一个整数。 否则,当两个操作数都是数字或可以被转换为数字的字符串 (参见 §3.4.3)时, 操作数会被转换成两个浮点数, 操作按通常的浮点规则(一般遵循 IEEE 754 标准) 来进行,结果也是一个浮点数。
乘方和浮点除法 (/
) 总是把操作数转换成浮点数进行,其结果总是浮点数。 乘方使用 ISO C 函数 pow
, 因此它也可以接受非整数的指数。
Lua 支持下列位操作符:
-
&
: 按位与 -
|
: 按位或 -
~
: 按位异或 -
>>
: 右移 -
<<
: 左移 -
~
: 按位非
Lua 对一些类型和值的内部表示会在运行时做一些数学转换。 位操作总是将浮点操作数转换成整数。 乘方和浮点除法总是将整数转换为浮点数。 其它数学操作若针对混合操作数 (整数和浮点数)将把整数转换为浮点数; 这一点被称为 通常规则。
当操作需要数字时,Lua 还会把字符串转换为数字。
等于操作不会将字符串转换为数字,反之亦然。 即,"0"==0
结果为 false
所有的逻辑操作符把 false 和 nil 都作为假, 而其它的一切都当作真。
逻辑操作符
Lua 中的逻辑操作符有 and, or,以及 not。
取反操作 not 总是返回 false 或 true 中的一个。
10 or 20 --> 10
10 or error() --> 10
nil or "a" --> "a"
nil and 10 --> nil
false and error() --> false
false and nil --> false
false or nil --> nil
10 and 20 --> 20
字符串连接
Lua 中字符串的连接操作符写作两个点('..
')
取长度操作符
取长度操作符写作一元前置符 #
。 字符串的长度是它的字节数(就是以一个字符一个字节计算的字符串长度)。
表构建
a = { [f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45 }
等价于
do
local t = {}
t[f(1)] = g
t[1] = "x" -- 1st exp
t[2] = "y" -- 2nd exp
t.x = 1 -- t["x"] = 1
t[3] = f(x) -- 3rd exp
t[30] = 23
t[4] = 45 -- 4th exp
a = t
end
构造子中赋值的次序未定义。 (次序问题只会对那些键重复时的情况有影响。)
函数定义
该语句
function f () body end
被转译成
f = function () body end
该语句
function t.a.b.c.f () body end
被转译成
t.a.b.c.f = function () body end
该语句
local function f () body end
被转译成
local f; f = function () body end
而不是
local f = function () body end
(这个差别只在函数体内需要引用 f
时才有。)
可见性规则
x = 10 -- 全局变量
do -- 新的语句块
local x = x -- 新的一个 'x', 它的值现在是 10
print(x) --> 10
x = x+1
do -- 另一个语句块
local x = x+1 -- 又一个 'x'
print(x) --> 12
end
print(x) --> 11
end
print(x) --> 10 (取到的是全局的那一个)
注意,每次执行到一个 local 语句都会定义出一个新的局部变量。 看看这样一个例子:
a = {}
local x = 20
for i=1,10 do
local y = 0
a[i] = function () y=y+1; return x+y end
end
这个循环创建了十个闭包(这指十个匿名函数的实例)。 这些闭包中的每一个都使用了不同的 y
变量, 而它们又共享了同一份 x
。
编程接口
这个部分描述了 Lua 的 C API , 也就是宿主程序跟 Lua 通讯用的一组 C 函数。 所有的 API 函数按相关的类型以及常量都声明在头文件 lua.h
中。
C 库中所有的 Lua API 函数都不去检查参数是否相容及有效。 然而,你可以在编译 Lua 时加上打开一个宏开关 LUA_USE_APICHECK
来改变这个行为。
栈
Lua 使用一个 虚拟栈 来和 C 互传值。 栈上的的每个元素都是一个 Lua 值 (nil,数字,字符串,等等)。每次调用的栈都是全新的。
栈大小
当你使用 Lua API 时, 就有责任保证做恰当的调用。 特别需要注意的是, 你有责任控制不要堆栈溢出。 你可以使用 lua_checkstack 这个函数来扩大可用堆栈的尺寸。