Sol”这个词在葡萄牙语(巴西官方语言)里是太阳的意思,而“Lua”的意思就是月亮。
知名的应用有Adobe Lightroom、Fire-fox、Redis等,而游戏则有《魔兽世界》《愤怒的小鸟》《我的世界》等。
Lua语言提供六种基本的数据类型
Lua的字符串形式非常灵活,单引号或者双引号都可以,字符串里也允许使用转义符
Lua还用“[[…]]”的形式支持raw string,括号内的字符不会转义,在写正则表达式或者字符串里有引号、斜线的时候非常方便
与多行注释类似,“[[…]]”的形式也支持在括号中间插入“=”,而且如果“[[”后面是一个换行,那么Lua会自动忽略这个换行,在书写大量文字时是个非常方便的特性
Lua字符串的另外一个特点是只读的
Lua语言在内部使用一个全局散列表来管理所有的字符串,所以多个相同的字符串不会占用多份内存
Lua语言里的变量有作用域的概念,分为局部变量和全局变量,名字区分大小写
局部变量需要使用关键字“local”声明,作用域仅限本代码块(文件内或语句块内),没有关键字“local”声明的变量都是全局变量,而且不需要声明就可以直接使用
在Lua里应当尽量少使用全局变量,多使用局部变量
因为“局部化”,解释器查找的速度也更快
一个比较常用的全局变量是:“_”(下画线,也是合法的变量名)
Lua语言里没有“常量”,实践中我们通常用全大写名字的变量来表示常量
Lua里的运算有算术运算、关系运算、逻辑运算、字符串运算等
不等比较使用的是“~=”
在执行大于或小于比较操作时Lua会检查变量的类型,如果类型不同就会出错
但“==”和“~=”的行为则不同,如果类型不同会直接返回false
为了避免发生意外,必须用函数tonumber()/tostring()显式转换数字或字符串后再做比较运算
Lua的逻辑运算符有and、or和not三个
nil和false认为是假,其他都是真,包括数字0
x and y,如果x是真,返回y,否则返回x
x or y,与and操作正好相反,如果x是真,返回x,否则返回y
not x,只返回true/false,对x取反
Lua对字符串连接操作提供了一个特别的运算符“..”
连接运算还可以自动把数字转换为字符串,无须显式调用tostring()函数:
字符串连接操作应当尽量少用,因为每一次字符串连接就会创建一个新的字符串对象
如果多次操作超长字符串(例如几十MB的大块数据)就可能会导致LuaVM内存耗尽,发生错误。
计算字符串的长度可以用另一个特别的运算符“#”
字符串的关系运算基于字符序(例如最常用的ASCII码表)逐个检查,但相等比较是直接计算内部保存的散列值
很多时候对nil运算都会导致错误
如果一个变量可能是nil,最好使用or运算给它一个默认值
Lua里的语句包括赋值语句、分支语句和循环语句
Lua语句的格式非常自由,不强制要求缩进。语句末尾可以使用“;”表示结束,但不是必需的
使用“do…end”的形式就声明了一个语句(代码)块
Lua使用“=”在变量里存储一个确定类型的值
Lua还允许用逗号分隔,在一个语句里声明或赋值多个变量
如果给一个变量赋值为nil,就表示删除了这个变量
Lua的分支语句只有一种,就是if-else
多重分支需要使用“elseif”
Lua语言的循环语句有while、repeat-until和for三种
repeat-until循环与while差不多,但条件判断与while意义是相反的
Lua的for循环语句有两种形式:数值循环和范围循环
for的数值循环类似其他语言的标准for语句,但形式上要简洁一些
变量var从m前进(或后退)到n,执行循环体里的语句,参数step是用于控制var前进或后退的步长,可以省略,默认是1
for循环里的变量var会自动声明为局部变量
可以用break或者return直接跳出循环,用法与其他语言相同,但必须在语句块的最后——也就是后面紧跟着end/until关键字
如果想要在任意的位置结束循环,可以使用“do break end”的形式
OpenResty使用的LuaJIT扩展支持goto,可以变通实现continue
在Lua语言里函数是一类特殊的变量,它持有一个语句块,能够使用参数执行语句块(也就是“调用”),然后返回结果
Lua的函数就是变量,也可以(最好)使用local局部化。
可以传入任意数量的实参,少的默认值是nil,多的则被忽略
函数的返回值使用return语句,可以用逗号分隔返回多个值,但如果被调用时使用圆括号包围则只会返回一个值
Lua函数的参数都是传值(但表除外)
如果调用时只有一个传入的参数,而且这个参数是字符串或者表,那么Lua允许省略函数的“()”,直接在函数名后写参数
但Lua的表更加灵活,能够模拟出array、list、dict、set、map等常见数据结构,或者其他任意复杂的结构
Lua表里作为索引的key可以是任何非nil值,所以当key类型是整数时表就相当于数组,key类型是字符串时表就相当于字典或关联数组
Lua表对value的类型没有任何限制,当然也可以是另外一个表,从而实现多个表的嵌套。
Lua里定义表使用花括号“{}”
直接使用“{}”就是一个空表,在里面简单地列出表内的元素就声明了一个数组形式的表,使用“key=value”的形式就可以声明为字典形式的表
使用“key=value”的形式时key不需要用单引号或双引号,如果必须要用(例如key里有空格或者其他特殊符号)则要使用“[key]=value”的形式
定义表时的逗号“,”也可以改用分号“;”,两者没有不同,但可以做一些形式上的区分
Lua的表是动态的数据结构,不仅可以访问已有的元素,也可以随时向表里添加或删除元素
操作表里的元素需要使用方括号“[]”
当表作为数组来使用时整数下标索引必须从1开始计数
如果key是字符串我们也可以直接使用点号“.”来操作
运算符“#”可以计算形式表里的数组元素数量,配合for循环可以实现遍历数组
对于字典形式的表,暂时没有办法能够直接获取元素的数量,使用“#”会返回0
for循环语句的第二种形式——范围循环主要用于遍历表里的元素,但需要两个标准库函数的配合:ipairs()和pairs()。
模块就是一个函数集合,通常表现为一个Lua表,里面有模块作者提供的各种功能函数,使用点号“.”即可访问
使用require函数可以加载模块,参数是模块所在的文件名(省略后缀)
通常我们需要用变量来保存require函数的返回结果
require在加载模块的同时会执行文件里的代码
如果使用字符串作为key,那么表本身就是对象,可以任意存储变量和函数
封装”方面,Lua不提供private、public这样的修饰词,表里的所有成员都是公开的
如果想要实现私有成员,那么可以在模块文件里用local修饰
“多态”特性对于Lua来说非常简单,由于表是动态的,里面的成员都能够在运行时随意替换,没有编译型语言静态绑定的烦恼。
继承”在Lua语言里是不提倡的特性,替代方案是使用“原型”(prototype)模式,从一个“原型”对象“克隆”出一个新对象,然后再动态变更其属性,从而达到与“继承”类似的效果。
“原型”模式需要使用Lua的高级特性“元表”(metatable)和函数setmetatable()。
元表描述了表的基本行为,有些类似C++或者Python里的操作符重载,我们需要用的是“__index”元方法,它重载了Lua里查找key的操作,也就是table.key。
函数setmetatable(t, meta)把表t的元表设置为meta并返回t。
如果meta里设置了“index”方法,那么对t的操作t.key也会同样作用到meta上,即meta.key。这样,表t就“克隆”了表meta的所有成员,表meta就成为了表t的“原型”。
可以通过下面的例子来进一步理解Lua的“原型”操作:
local proto = {} — 首先声明一个原型对象,暂时是空表
function proto.go() — 为表添加一个方法,即成员函数
print(“go pikachu”)
end
local mt = { index = proto } — 定义元表,注意重载了“index”
local obj = setmetatable({}, mt) — 调用setmetatable设置元表,返回新表
obj.go() — 新对象是原型的“克隆”,可以执行原型的操作
代码里的关键操作是定义元表mt,里面只需要设置“index”方法,然后再使用函数setmetatable从mt克隆出一个新的对象。
这两个步骤也可以合并为一次操作:
Lua为面向对象的方式使用表内成员函数提供一个特殊的操作符“:”,它的功能与“.”基本相同,但在调用函数时会隐含传入一个“self”参数
“:”和self不仅可以用在函数调用时,也可以用在函数定义时
“:”其实是一种“语法糖”,是简化的“.”写法
建议尽量使用“:”,它更简洁一些
应该用OpenResty自己的ngx.re系列函数
过table.insert的效率并不高,我们可以用如下的方式更高效地在末尾添加元素:
a[#a + 1] = ‘nginx’ — 利用“#”运算符获取长度来添加元素
io库里是操作文件的函数,由于文件通常存储在磁盘上,而且是阻塞操作,速度很慢,在OpenResty里应当尽量少用。
在读取数据时,可以使用参数“a”(即all)读取整个文件,或者“l”(即line)读取一行,使用数字则读取指定长度的字节
os库包含有操作系统和时间日期相关的函数
形象(但不很准确)地来说,“闭包”就是一个“活的函数”,存在于程序的“高维空间”,可以任意操作函数外部的数据
如果想要Lua代码更加健壮,我们可以使用保护模式来执行可能出错的函数
pcall(protected call)是base库里的一个特殊函数,它“保护调用”一个函数,绝对不会出错,并以true/false返回调用结果