Lua 于 1993 年诞生在 Pontifical Catholic University of Rio de Janeiro in Brazil ,意为月亮,是一门卫星型寄生式可扩展轻量脚本语言。
1 基本语法
- 变量名大小写敏感。
- 变量名没有类型,变量值才有类型,运行时允许变量名和任意类型的变量值绑定。
- 变量类型一共 8 个:
nil, boolean, number, string, userdata, function, thread, table
,由函数 type() 确定。 - 语句可以不换行,仅当不换行时语句末尾需要加分号,换行时加不加无所谓。
- chunk 是一组有边界的连续关联语句的集合,可大可小,本质是匿名函数。
- 用 local 声明的局部变量只在当前 chunk 中有效。
do...end
相当于 C/C++ 的{}
,可以控制局部变量的有效范围。 - environment 表存放所有全局变量。全局变量允许随时随地无中生有,未初始化的全局变量等于 nil 。
- 全局变量 _G 存放环境变量,全局变量 arg 存放命令行参数。听说 arg 有时也表示所在函数的变量?
- 单行注释用
--...
,多行注释用--[[...--]]
。
1.1 基本类型
1.1.1 number
实数。
print(5/10) --> 0.5
1.1.2 string
- 单双引号都可以表示字符串。
[[...]]
可以表示多行字符串,其间内容不转义。括号有级别,同级才能配对,[[...]]
为 0 级,[=[...]=]
为 1 级,[==[...]==]
为 2 级,依次类推。- 字符串之间用
..
连接。注意试图以字符串的形式连接数值时,必须加上空格以防解释出错。 - 运算途中,数值和字符串之间会自动转换。
str = [=[string have a [[]].]=]
print(str) -->string have a [[]].
两个完全一样的 Lua 字符串内化(intern)到 Lua 虚拟机中只会存储一份,这意味着创建相同的 Lua 字符串不会引入新的动态内存分配操作,已经创建好的 Lua 字符串之间比较的时间复杂度是 O(1) 而不是通常的 O(n) 。
1.1.3 nil
- nil 表示空,而不是常见的 null 。
- 给变量赋值 nil 等于删除它。
- 允许多个变量同时赋值。若变量的个数小于值的个数,则多余的值将被忽略;若变量的个数大于值的个数,则多余的变量将用 nil 补足。
1.1.4 boolean
- 所有类型的变量都有 boolean 值,都能参与 and or not 三个逻辑运算。
- 0 和空字符串的 boolean 值是真。
不同于 C 语言, Lua 中的 and 和 or 除真假两个值外, nil 也以特殊的方式参与运算: a and b
如果 a 为 nil,则返回 a,否则返回 b ; a or b
如果 a 为 nil,则返回 b,否则返回 a 。
1.1.5 table
- table 既能当 list ,又能当 dict ,甚至还能二不像。
- table list 下标从 1 开始,求长符号
#
只能计算 list 从 1 开始的长度。 - table dict 不会保留值为 nil 的键。
- table 用
t == nil or next(t) == nil
判空。
1.1.6 function
函数定义的本质是变量赋值,具名函数的本质是将匿名函数赋值给变量。
function foo() ... end <=> foo = function () ... end
参数值相关:
- 因为变量的定义总在变量使用之前,所以函数的定义也在函数使用之前。
- 当函数有多个参数时,若参数的个数小于所需参数的个数,则多余的参数将被忽略,若参数的个数大于所需参数的个数,则多余的所需参数将用 nil 补足。
...
表示变长参数,但在使用前须用{...} or {}
转成 table 。- 函数参数除了 table 按址传递外,都是按值传递。
- 当函数作为唯一参数或末尾参数时,根据变量个数多退少补。其它情况一般只返回第一个值或返回 nil , 。
返回值相关:
- 函数返回的字符串永远是新字符串,原字符串不受影响。
- 虚变量
_
可以占位表示不需要的返回值,允许重复使用。 - 函数的返回值视具体情况多退少补,除非
return f()
或 unpack ? 使用圆括号可以强制只返回第一个值。
local function init()
return 1, "lua"
end
local x, y, z = init(), 2
print(x, y, z) -->output 1 2 nil
local a, b, c = 2, init()
print(a, b, c) -->output 2 1 lua
print((init()), 2) -->output 1 2
print(2, (init())) -->output 2 1
1.1.7 userdata
略。
1.1.8 thread
略。
1.2 表达式
- 不等号是 ~= 而不是 != 。
- table 、 function 、 userdata 的比较是引用比较。
1.3 控制结构
控制结构的 end 非常容易遗漏,建议先写 end 再填充。
1.3.1 选择结构
注意选择结构的 elseif 要连写。
if conditions then
print("Hello World!")
elseif conditions then
print("Hello World!")
else
print("Hello World!")
end
1.3.2 循环结构
循环过程中不要试图改变控制变量的值。
while condition do
print("Hello World!")
end
repeat
print("Hello World!")
until conditions
for var=start,end,step do
loop-part
end
for i,v in ipairs(a) do
print(i)
print(v)
end
for k,v in pairs(t) do
print(k)
print(v)
end
跳出循环的方法只有 break 、 return 、 goto 。没有 continue ,但可用 goto 实现。
for i=1, 3 do
if i <= 2 then
print(i, "yes continue")
goto continue
end
print(i, " no continue")
::continue::
print([[i'm end]])
end
更多 goto 脑洞见 GotoStatement 。
2 math
兄弟会欺骗你,妻子会背叛你,但数学不会,不会就是不会。
所以请放心用。
3 string
3.1 常用方法
- byte/char 互反,仅适用 ASCII 码。
- lower/upper 大小写转换。
- len 返回字符串长度。不过更应使用 # 来获取字符串长度。为什么?因为前者是计算,后者是获取?
- rep 将字符串复制几遍。好像没什么用?
- reverse 反转。
- format 格式化。
- sub 参数 i 和参数 j 都是索引,j 不是长度,二者均可负。
由于 Lua 放弃理解具体的字符集编码,因此对于含有多字节字符的字符串,以上方法均不能正常工作。
3.2 正则匹配
Lua 正则不是 Posix 标准正则!
与正则相关的常用函数有:
i, j, str = string.find(str, pattern[, init[, plain]])
string.gsub(str, pattern, repl[, n]) -- 与 string.sub(s, i [, j]) 无关
string.match(str, pattern[, init])
string.gmatch(str, pattern)
- 转义字符是
%
不是\
。 %bxy
表示平衡匹配(匹配 xy 对)。这里的 x,y 可以是任何字符,即使是特殊字符也是原来的含义,匹配到的子串以 x 开始,以 y 结束,并且如果从 x 开始,每遇到 x ,计算 +1 ,遇到 y 计数 -1 ,则结束的 y 是第一个 y 使得计数等于 0 。就是匹配成对的符号,常见的如%b()
匹配成对的括号跟预定义字符集不同,在[]
中失去特殊意义。- 字符类除了 %b 以外的大写形式表示取反,也就是取小写形式匹配集合的补集。
- 不支持或运算
|
, - 不支持命名组。
- 不支持分组后面接重复词
()+*?
或指定重复次数{m, n}
,对于复杂的匹配只能用 find 手动处理。
4 table
4.1 getn/maxn
计算 table 的长度是一个未定义的行为!任何一个 nil 值都有可能被当成数组的结束,当 list 中存在空洞时,长度是不确定的!
4.2 insert/remove
略。
4.3 sort
table.sort() 的本质是冒泡排序,仅适用于 list ,对 dict 和二不像无效,即使 key 全是 number 也不行。其 comp 函数不能出现等于,包括小于等于和大于等于。
If comp is given, then it must be a function that receives two list elements and returns true when the first element must come before the second in the final order (so that, after the sort,
i < j
implies notcomp(list[j],list[i])
)
4.4 concat
由于 Lua 字符串本质上是只读的,因此字符串连接运算符几乎总会创建一个更大的新字符串,多次连接的性能损耗非常大,这种情况推荐使用 table.concat() 来进行拼接。
注意 concat 只处理 list 中从 1 开始的连续下标的数据。
5 时间
务必小心分秒的 key ,用的是缩写 min 和 sec 。
timestamp = os.time()
timestamp = os.time({
year = some_year,
month = some_month,
day = some_day,
hour = some_hour,
min = some_min, -- minute!
sec = some_sec, -- second!
})
datetime = os.date("%Y-%m-%d %H:%M:%S", timestamp)
difftime = os.difftime(timestamp1, timestamp2)
注意,以下计算时间的方式是错误的:
d = math.floor(difftime / 60 / 60 / 24)
h = math.floor(difftime / 3600) % 24
m = math.floor(difftime / 60) % 60
s = math.floor(difftime % 60)
再补充一些常用时间。
-- 当前时间
local int_current = os.time()
local str_current = os.date('%H:%M:%S', os.time())
-- 昨天,今天,明天
local str_today = os.date('%Y-%m-%d', os.time())
local tab_today = os.date('*t', os.time())
local str_yesterday = os.date('%Y-%m-%d', os.time() - 24*60*60)
local tab_yesterday = os.date('*t', os.time() - 24*60*60)
local str_tomorrow = os.date('%Y-%m-%d', os.time() + 24*60*60)
local tab_tomorrow = os.date('*t', os.time() + 24*60*60)
-- 今天零点,今天末点
local tab_today = os.date('*t', os.time())
local int_today_000000 = os.time({
year = tab_today.year,
month = tab_today.month,
day = tab_taday.day,
hour = 0,
min = 0,
sec = 0,
})
local int_today_235959 = os.time({
year = tab_today.year,
month = tab_today.month,
day = tab_taday.day,
hour = 23,
min = 59,
sec = 59,
})
6 类
Lua 只有用 table 模拟的假类,没有真类。
-- my.lua:
local _M = {}
local function get_name()
return "Chittle Skuny"
end
function _M.greeting()
print("Hello " .. get_name())
end
return _M
-- main.lua:
local my_module = require "my"
my_module.greeting()
--> output: Hello Chittle Skuny
当 lua_code_cache on 开启时,require 加载的模块是会被缓存下来的,这样我们的模块就会以最高效的方式运行。
抵制 module("filename", package.seeall)
这种写法。一是因为 package.seeall 破坏了模块的高内聚,引入 filename 模块原本只想调用它的 foobar() 函数,但是却能读写全局属性,例如 “filename.os” 。二是因为 module 函数压栈操作的副作用,它会创建一个 filename 的 table 并污染全局环境变量,使得没有引用它的文件也能调用 filename 模块的方法。
要特别注意 .
和 :
的区别: class.func1(self, a, b, c) <=> class:func2(a, b, c)
。
function MainScene:func(a, b, c)
print(a, b, c)
end
function MainScene:test()
self:func(1, 2, 3)
end
--> output: 1 2 3
function MainScene.func(a, b, c)
print(a, b, c)
end
function MainScene:test()
self:func(1, 2, 3)
end
--> output: userdata 1 2
-- self->a, 1->b, 2->c, 3->nil
function MainScene.func(a, b, c)
print(a, b, c)
end
function MainScene:test()
self.func(1, 2, 3)
end
--> output: 1 2 3
function MainScene:func(a, b, c)
print(a, b, c)
end
function MainScene:test()
self.func(1, 2, 3)
end
--> output: 2 3 nil
-- 1->self, 2->a, 3->b, nil->c
虽然 lua 支持继承但是千万别碰,引入的问题一定比解决的问题多。