较为系统的过一遍 Lua

gomkiri 发布于 2025-10-31 59 次阅读


AI 摘要

从Redis脚本到游戏开发,Lua为何成为多领域利器?本文系统梳理Lua核心特性:table索引从1开始、多返回值函数、闭包迭代器、原子性保证。带你掌握这个轻量高效的脚本语言,告别AI依赖,真正理解Lua编程精髓。

在进行 Java 开发的过程中,涉及到 Redis 操作就不可避免的会提到 Lua 脚本,这是目前最常用的保证多条 Redis 语句执行过程中的原子性的手段。但是在以往的使用中,大都是直接借助 AI 来完成 Lua 语句的编写,但是随着需要的 Lua 脚本越来越复杂,还涉及到了函数的内容,所以还是想在这里再较为系统的过一遍 Lua 语言(在以往的使用中,我们通常是直接以 Lua 脚本的方式来称呼 Lua,在这里还是想再说一下,Lua 是一个系统的脚本语言,他还能应用于游戏开发、应用脚本、安全系统)

基本语法

  1. Lua 脚本文件直接以.lua作为后缀名完成脚本式编程
  2. 注释方式:
    单行注释:--
    多行注释:--[[ ]]
  3. 变量的定义方式与 Java 一致
  4. 关键词从 Java 的角度来看有一个可以注意一下的:elseif / nil / until / in / functin / end
  5. Lua 语言中的变量皆为全局变量,不需要声明、直接使用。

数据类型

Lua 中的数据类型有:nil / boolean / number / string / function / userdate / thread / table

以 Java 程序员的角度出发讲一讲:

  1. nil 就相当于是 null,表示一个无效值,同时在条件表达式中相当于 false。
  2. number 大概相当于 java 中 double
  3. String,使用单引号和双引号都可
  4. function,就是一个函数
  5. userdate,存储在变量中的 c 数据结构 ???(留一个疑问)
  6. thread,可以理解为开启一个线程,用于完成协同程序
  7. 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.igettable_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] = 60
Lua

另外 Lua 还提供了几个操纵表的方法:

  1. table.remove(),只使用一个参数表名,要是删除表中最后一个元素,加上第一个参数 index,用于删除指定索引的值,删除后后面的值自动往前补
  2. table.sort(table),给 table 进行升序排序
  3. table.insert(table,[index],value),插入新的数据,默认尾差,也可以指定插入位置
  4. table.concat(table , sep , start , end),提取 table 中从 start 到 end 的元素,元素之间使用 sep 分割

基本语句

循环:

Lua 中的循环方式有三种:while / for / repeat … unti

循环控制语句有两种:breakgoto

下面的使用示例:

-- 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 ...]
end
Lua

一个令人关注的特殊点是: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))  --->25
Lua

(就好像我们传入了一个变量名为 ...的 table)
这种理解很如何我们的第一直觉,但是还是有些差异,比如我们可以通过for i , v ipairs {...}do对参数进行遍历,也可以使用local arg = {...}创建一个记录了参数的局部变量,但是我们却不能直接使用...[1]来获取参数中的数据。

运算符

运算符部分基本与 Java 一致,这里只讲有冲突的地方:

算术运算符:

  1. 除法:/,得到的结果是真正的除法结果,而非整数部分。
  2. 整除://,与 java 的 / 一致,得到整数部分
  3. 乘幂:^,不是位运算符,lua 中没有位运算符(吧),A ^ 2表示 A 的二次方。

关系运算符:

  1. ~=:不等于,不是 !=

逻辑运算符:

  1. and --> &&
  2. or --> ||
  3. not --> !

其他运算符:

  1. .. : 用于连接两个字符串
  2. # : 返回字符串或表的长度,例如#"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)
end
Lua

上面示例中的 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)
end
Lua

这里的 count 和 count 都是迭代器的状态,由迭代器自己去记录自己遍历到哪里了,自己何时该退出。

错误处理

首先,Lua 提供了两种检查和生成异常的方式:

  1. assert(boolean , string),如果第一个参数为 false, 则抛出异常,异常信息为第二个参数
  2. error(message , [level]) ,直接抛出错误,错误信息为 message , 第二个参数可选值和作用如下:
    • 1,声明发生错误的包名和行号
    • 2,指出 error 函数的被调用来自哪个函数
    • 0,不附带任务信息

对于调用函数可能发生错误的预处理,Lua 也提供了方法:

if pcall(function_name, ….) then
-- 没有错误
else
-- 一些错误
end
Lua

如果在函数执行过程中发生了异常,就会直接进入 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        nil
Lua

debug 库的俩个错误处理函数解释:

  1. debug.debug:提供一个Lua提示符,让用户来检查错误的原因
  2. debug.traceback:根据调用栈来构建一个扩展的错误消息
小码农 & GPT调教糕手
最后更新于 2025-11-01