在进行 Java 开发的过程中,涉及到 Redis 操作就不可避免的会提到 Lua 脚本,这是目前最常用的保证多条 Redis 语句执行过程中的原子性的手段。但是在以往的使用中,大都是直接借助 AI 来完成 Lua 语句的编写,但是随着需要的 Lua 脚本越来越复杂,还涉及到了函数的内容,所以还是想在这里再较为系统的过一遍 Lua 语言(在以往的使用中,我们通常是直接以 Lua 脚本的方式来称呼 Lua,在这里还是想再说一下,Lua 是一个系统的脚本语言,他还能应用于游戏开发、应用脚本、安全系统)
基本语法
- Lua 脚本文件直接以
.lua作为后缀名完成脚本式编程 - 注释方式:
单行注释:--
多行注释:--[[ ]] - 变量的定义方式与 Java 一致
- 关键词从 Java 的角度来看有一个可以注意一下的:
elseif/nil/until/in/functin/end - Lua 语言中的变量皆为全局变量,不需要声明、直接使用。
数据类型
Lua 中的数据类型有:nil / boolean / number / string / function / userdate / thread / table
以 Java 程序员的角度出发讲一讲:
- nil 就相当于是 null,表示一个无效值,同时在条件表达式中相当于 false。
- number 大概相当于 java 中 double
- String,使用单引号和双引号都可
- function,就是一个函数
- userdate,存储在变量中的 c 数据结构 ???(留一个疑问)
- thread,可以理解为开启一个线程,用于完成协同程序
- table,关联数组,下面有详细描述
table:
Lua 中的 table 像 Java 中的数组,又像 Java 中的 Map,为什么这么说呢,因为它可以以这种方式定义:local tbl = {"apple", "pear", "orange", "grape"} ,又可以以这种方式来使用:a = {} a["key"] = "value" ,是的你没有看错,table 可以直接以 String 作为索引。
并且 table 没有固定的长度大小,没初始化的 table 就是 nil,使用 tbl[key] = val 的方式就能添加新的元素。另外,除了使用方括号来使用索引,其实还有两种使用方式:table.i 和 gettable_event(table,i)。第一次编写在这里漏掉了一个很重要的点,在这里补充:table 的索引是从 1 开始的,而不是 0 !
可以使用 #tableName 获取数组的长度,然后完成遍历、新增等操作:
local myArray = {10 , 20 ,30 , 40 , 50}
local length = #myArray
for i = 1 , length do
print(myArray[i])
end
-- 添加新元素到数组末尾
myArray[length] = 60Lua另外 Lua 还提供了几个操纵表的方法:
table.remove(),只使用一个参数表名,要是删除表中最后一个元素,加上第一个参数 index,用于删除指定索引的值,删除后后面的值自动往前补table.sort(table),给 table 进行升序排序table.insert(table,[index],value),插入新的数据,默认尾差,也可以指定插入位置table.concat(table , sep , start , end),提取 table 中从 start 到 end 的元素,元素之间使用 sep 分割
基本语句
循环:
Lua 中的循环方式有三种:while / for / repeat … unti
循环控制语句有两种:break 和 goto
下面的使用示例:
-- while 循环
while(condition)
do
statements
end
-- for 循环 - 数值 for 循环
-- 这种循环方式跟 java 的有点像:第一个位置定义变量,第二个位置是一个特殊的循环条件,只有当定义的数值等于这个数才会终止循环,第三个数是步长,可以省略,默认为1。
for i = 1 , 10 , 1 do
print(i)
end
-- 上面的语句表示,i 的初始值为 1,每次循环 i 加一,直到 i == 10
-- for 循环 - 泛型 for 循环
-- 这种循环方式类似于 java 的 forEach 语句,下面是一种遍历 table 的方式,每次都获取到 key 和 value(其中的 ipaires 是 Lua 的迭代器函数):
a = {"one","two","three"}
for key , val in ipairs(a) do
print(key , val)
end
-- repeat...until 循环,类似于 java 中的 do...while ,也是先执行一次,但是终止条件刚好相反:当 condition == true 时跳出循环
repeat
statements
until( condition )break语句完全一致,不再赘述,来看看 goto 语句,作用大家都懂主要是看看标签的定义方式:
-- 通过将:: LableName ::放在语句前面的方式来定义跳转点
local a = 1
::labal:: print("goto lable")
a = a + 1
if a < 3 then
goto lable
end流程控制语句:
也就是常见的 if else语句,一块来看看在 Lua 中的写法:
-- if 写法
if(boolean)
then
语句
end
-- if...else 写法
if(boolean)
then
语句
else
语句
end
-- if... else if 写法
if(boolean)
then
语句
elseif(boolean)
then
语句
else
语句
end函数
Lua 中的函数不需要定义返回值类型,没有复杂的包装特性,唯一有的就是可以在函数定义之前加上一个local表示这是一个局部函数,而非全局函数,下面是一个 Lua 函数的基本结构示例:
[local] function function_name(参数) -- 值得注意的是这里的参数不需要写类型,直接写参数名即可
函数体
[return ...]
endLua一个令人关注的特殊点是:Lua 支持多返回值,这与 Java 中的返回值最多只能有一个是很大不同的,我们在使用 Lua function 时,可以在 return 后面写多个变量,比如 return m , n,这样的话,我们就需要使用俩个参数来接受返回值:a , b = myFunction()
如果参数数量不可控,我们还可以使用...作为参数列表来接收任意个参数,然后使用迭代器来遍历参数:
function add(...)
local s = 0
for i, v in ipairs{...} do --> {...} 表示一个由所有变长参数构成的数组
s = s + v
end
return s
end
print(add(3,4,5,6,7)) --->25Lua(就好像我们传入了一个变量名为 ...的 table)
这种理解很如何我们的第一直觉,但是还是有些差异,比如我们可以通过for i , v ipairs {...}do对参数进行遍历,也可以使用local arg = {...}创建一个记录了参数的局部变量,但是我们却不能直接使用...[1]来获取参数中的数据。
运算符
运算符部分基本与 Java 一致,这里只讲有冲突的地方:
算术运算符:
- 除法:
/,得到的结果是真正的除法结果,而非整数部分。 - 整除:
//,与 java 的 / 一致,得到整数部分 - 乘幂:
^,不是位运算符,lua 中没有位运算符(吧),A ^ 2表示 A 的二次方。
关系运算符:
~=:不等于,不是 !=
逻辑运算符:
and-->&&- or -->
|| - not -->
!
其他运算符:
..: 用于连接两个字符串#: 返回字符串或表的长度,例如#"Hello"返回5
变量
变量可以分为全局变量和局部变量,全局变量直接赋值就可,局部变量在声明是前面加一个local (与局部函数一致)
变量的基本赋值方法与 Java 一致,等号一连接就完事,而且 Lua 中的变量不需要特意单独声明,在函数等场景使用之前赋过值就行,但是 Lua 还支持了同时对多个变量赋值,例如:a ,b = 1, 3 ,这样就给 a 赋值了 1 ,给 b 赋值了 3,但是要注意等号两边的数量要相等,如果出现a , b = 1 ,那么 b 的值就为 nil
迭代器
最简单的使用ipairs()的迭代器我们刚才已经见过了,这其实是一个典型的无状态的迭代器,所谓的无状态意思就是在迭代函数执行的过程中,不会存储任何的记录,每次都是直接传入新的值并完成新的计算,可以再来看一看下面这个使用函数实现的迭代器:
function square(iteratorMaxCount,currentNumber)
if currentNumber<iteratorMaxCount
then
currentNumber = currentNumber+1
return currentNumber, currentNumber*currentNumber
end
end
for i,n in square,3,0
do
print(i,n)
endLua上面示例中的 3 和 0分别代表了第一次执行 square 函数的两个入参,在第一次循环中 0 变成了 1,所以第二次循环会将 3 和 1 作为函数的参数,知道完成三次循环后没有触发 if 语句,迭代器没有返回值就代表遍历结束。
下面的是一个多状态的迭代器,所谓的多状态,就是说有一个全局变量,每次遍历都会使用,并且这个值与每次的入参无关,为了达到这一目的,我们要引入一种新的函数:闭包函数。看代码:
array = {"Google", "Runoob"}
function elementIterator (collection)
local index = 0
local count = #collection
-- 闭包函数
return function ()
index = index + 1
if index <= count
then
-- 返回迭代器的当前元素
return collection[index]
end
end
end
for element in elementIterator(array)
do
print(element)
endLua这里的 count 和 count 都是迭代器的状态,由迭代器自己去记录自己遍历到哪里了,自己何时该退出。
错误处理
首先,Lua 提供了两种检查和生成异常的方式:
assert(boolean , string),如果第一个参数为 false, 则抛出异常,异常信息为第二个参数error(message , [level]),直接抛出错误,错误信息为 message , 第二个参数可选值和作用如下:- 1,声明发生错误的包名和行号
- 2,指出 error 函数的被调用来自哪个函数
- 0,不附带任务信息
对于调用函数可能发生错误的预处理,Lua 也提供了方法:
if pcall(function_name, ….) then
-- 没有错误
else
-- 一些错误
endLua如果在函数执行过程中发生了异常,就会直接进入 else 语句。另外 Lua 也提供了另一种检错方式:xpcall 函数,他有两个基本参数,第一个是执行的函数,第二个是错误处理函数,类似于上面的 else 语句中的内容,但是不同的是,在这个函数中可以使用 debug 库提供的错误处理函数,后面的参数都是执行函数的入参,看下面这个示例:
xpcall(
function(i)
print(i)
error('error..')
end,
function()
print(debug.traceback())
end,
33)Lua他的结果如下:
stack traceback:
stdin:1: in function <stdin:1>
[C]: in function 'error'
stdin:1: in function <stdin:1>
[C]: in function 'xpcall'
stdin:1: in main chunk
[C]: in ?
false nilLuadebug 库的俩个错误处理函数解释:
debug.debug:提供一个Lua提示符,让用户来检查错误的原因debug.traceback:根据调用栈来构建一个扩展的错误消息
Comments NOTHING