19.Lua语法知识总结

19.总结


19.1 核心要点速览

分组、注释和打印

功能 语法/示例 说明
单行注释 -- 注释内容 --[=[ 可临时关闭注释
多行注释 --[[ 多行注释内容 ]] 嵌套注释需用 --[=[ … ]=]
打印输出 print("Hello Lua") 自动拼接参数(如 print(1, "a") 输出 1 a

变量类型

Lua 共有 8 种变量类型,均为动态类型(赋值时自动推导,无需显式声明):

类型 核心特点 & 示例 特殊说明
nil 唯一值 nil,标识“空”
示例:local a(默认 nil)、a = nil(释放变量)
未赋值变量默认是 nil;给变量赋 nil 可回收其内存
boolean truefalse 两个值
示例:local flag = true
真假规则:仅 nilfalse 为“假”,其余(如 0、空字符串 ""、空表 {} )均为“真”
number 统一数值类型(整数 + 浮点数,内部以双精度浮点数存储,Lua5.3+ 支持整数区分)
示例:local num = 10(整数)、local pi = 3.14(浮点数)
语法无整数/浮点数区分,运算自动适配(如 5/2=2.55//2=2 是整除)
string 字符串,用引号("abc"'abc')或长字符串语法 [[ 多行内容 ]] 定义
示例:local str = "Hello"local text = [[Lua 多行]]
不可变(修改需重新生成);拼接用 ..(如 "Hi" .. "Lua" );长度用 #(如 #"abc"=3
function 函数,支持匿名函数、闭包
示例:
function add(a,b) return a+b end(命名函数)
local f = function() print("匿名") end(匿名函数)
可作为参数/返回值(高阶函数);闭包可捕获外部变量(如 local x=1; function f() x=x+1 end
table 核心复合类型,兼具数组(顺序结构)和字典(键值对)功能
示例:
local arr = {1,2,3}(数组,索引从 1 开始)
local dict = {name="Lua", ver=5.4}(字典,通过 dict.name 访问)
动态扩容,无固定长度;是 Lua 唯一的复合数据结构,需用 table 库操作(如 table.insert 插入)
userdata 自定义用户数据(用于 C/C++ 扩展),存储外部语言(如 C)的内存数据
示例:(需结合 Lua C API 实现,纯 Lua 代码无直接示例)
需通过 C 扩展创建,Lua 脚本中仅作为“黑盒”对象使用(如游戏引擎的自定义资源对象)
thread 协同程序(coroutine),非系统线程,是协作式多任务的载体
示例:
local co = coroutine.create(function() print("协程") end)
通过 coroutine.resume 启动,coroutine.yield 暂停;同一时间仅一个协程运行,实现“伪并发”
  • 基础类型(nilbooleannumberstring)是“值类型”,赋值时拷贝内容;
  • 复杂类型(functiontableuserdatathread)是“引用类型”,赋值时传递引用(修改会影响原对象)。

字符串

字符串基础(声明、长度、多行)

功能 语法/示例 关键说明
声明方式 str1 = "双引号"
str2 = '单引号'
str3 = [[长字符串(支持换行)]]
双引号/单引号功能一致;[[...]]可多行,无需转义
长度计算(#) print(#"aBcdEfG字符串") → 16(汉字占3字节,UTF-8编码) #统计字节数(非字符数),如”中”占3字节→#"中"=3
多行打印 print("123\n456")(转义符\n
str = [[我是\n韬]](长字符串保留换行)
转义符\n显式换行;[[...]]直接保留换行格式

字符串拼接

方式 语法/示例 特点说明
运算符 .. print("123" .. "456") → 123456
str1=111; print(str1 .. 222) → 111222
自动拼接,支持非字符串(自动转字符串)
string.format print(string.format("我今年%d岁", 18)) → 我今年18岁 占位符拼接:%d(数字)、%s(字符串)、%a(任意)

类型转换(其他类型→字符串)

方法 语法/示例 说明
tostring a=true; print(tostring(a)) → "true"
num=123; print(tostring(num))
强制转换任意类型为字符串(nil转”nil”)

字符串库函数(string.xxx

方法名 语法/示例 关键说明 注意事项
转大写 upper string.upper("abC") → "ABC" 所有字符转大写(仅ASCII字母有效) 不修改原字符串
转小写 lower string.lower("AbC") → "abc" 所有字符转小写(仅ASCII字母有效) 同上
翻转 reverse string.reverse("abc") → "cba" 反转字符串字节顺序(汉字会乱码,因UTF-8多字节) 索引从1开始(Lua通用规则)
查找 find string.find("abCdef", "Cde") → 3 5(返回起始、结束索引) 查找子串位置,无匹配返回nil 区分大小写
截取 sub string.sub("abCdef", 3) → "Cdef"
string.sub("abCdef", 3, 4) → "Cd"
截取子串,省略结束索引则到末尾 索引从1开始,支持负数(倒数)
重复 rep string.rep("ab", 3) → "ababab" 重复拼接字符串 次数需为非负整数
替换 gsub string.gsub("abCdeC", "C", "**") → "ab**de** 2"(返回新串+替换次数) 全局替换子串,可指定替换次数(第3参数) 返回两个值:新串、替换次数
转ASCII byte string.byte("Lua", 1) → 76(取第1个字符的ASCII码) 提取字符的ASCII码(多字节字符仅取首字节) 索引可选(默认1)
ASCII转字符 char string.char(76, 117, 97) → "Lua" 将ASCII码转为字符 支持多参数,按顺序拼接

运算符

类型 运算符 示例/特性
算术 + - * / % ^ 5^2 = 25(幂)、7%3 = 1(取余)
比较 == ~= > < >= <= nil == false → false(仅 nil/false 为假)
逻辑 and or not 短路特性(如 true and false → false
位运算 & | ~ << >> Lua5.3+ 支持(如 8>>1 = 43&1 = 1
三目 不支持 -

条件分支语句

if 条件1 then
    -- 分支1
elseif 条件2 then
    -- 分支2
else
    -- 分支3
end

真值规则:仅 nilfalse 为假,其余(0、空字符串等)均为真。

循环语句

类型 语法模板 执行逻辑 示例代码(简化) 核心特点/注意事项
while while 条件 do
  -- 循环体
end
先判条件,真则执行循环体 num=0; while num<5 do print(num); num=num+1 end 可能一次不执行;需手动更新条件(否则死循环)
repeat-until repeat
  -- 循环体
until 条件
先执行循环体,再判条件,真则退出 num=0; repeat print(num); num=num+1 until num>5 至少执行一次;退出条件与C# do-while 相反(Lua“条件真→退出”,C#“条件假→退出”)
数值for for 变量 = 初始值, 终止值, 步长 do
  -- 循环体
end
变量按步长增减,超终止值(方向由步长定)则退出 -- 递增(步长1):
for i=2,5 do print(i) end
-- 自定义步长:
for i=1,5,2 do print(i) end
-- 递减:
for i=5,1,-1 do print(i) end
步长可选(默认1);变量局部作用域;
初始值与步长方向矛盾则不执行(如 for i=1,5,-1

细节补充

  • 数值for的终止规则:
    • 步长为正 → 变量 > 终止值 时退出(如 for i=1,5i=6 退出);
    • 步长为负 → 变量 < 终止值 时退出(如 for i=5,1,-1i=0 退出);
    • 若初始值与步长方向矛盾(如 for i=3,1,1),循环直接跳过
  • 死循环处理:while true / repeat ... until false 需用 break 手动终止。

函数核心特性

声明与调用方式

类型 声明语法 调用语法 示例
命名函数 function 函数名(参数) ... end 函数名(参数) function add(a,b) return a+b end
add(1,2)
匿名函数 local 变量 = function(参数) ... end 变量(参数) local f = function() print("匿名") end
f()

参数处理

场景 规则说明 示例代码 输出结果
常规参数 按顺序传递,无需声明类型 function F(a) print(a) end
F("str")
str
参数不匹配 多传则丢弃,少传补nil F()
F(1,2,3)
nil
1(丢弃2、3)
变长参数 ...接收,存入arg表(Lua5.1+)或自定义表 function F(...) local t={...} print(#t) end
F(1,"a",true)
3(表长度)

返回值机制

类型 语法示例 调用与接收方式 注意事项
单返回值 function F(a) return a*2 end local res = F(5)
print(res)
10
多返回值 function F(a) return a, "ok", true end local x, msg, flag = F(10)
print(x, msg, flag)
10 ok true
变量匹配 接收变量不足则忽略多余返回值,多余变量补nil local x = F(10)
print(x)
10(忽略”ok”、true

特殊特性

特性 说明 示例代码 核心逻辑
函数类型 变量类型为function local f = function() end
print(type(f))
function
重载限制 同名函数后声明覆盖前声明 function F() print(1) end
function F() print(2) end
F()
2(调用最后声明的函数)
函数嵌套 内部函数可返回给外部 function F() return function() print("嵌套") end end
local f = F()
f()
嵌套
闭包 内部函数捕获外部变量 function F(x) return function(y) return x+y end end
local f = F(10)
print(f(5))
15(x=10被持久化)

闭包核心示例

-- 闭包捕获外部变量x,形成持久化状态
function makeAdder(x)
    return function(y)
        return x + y  -- 即使makeAdder执行完毕,x仍被保留
    end
end

local add10 = makeAdder(10)
local add20 = makeAdder(20)
print(add10(5))  -- 15
print(add20(5))  -- 25

关键:闭包延长了外部变量的生命周期,不同闭包实例独立维护各自的捕获变量(如add10add20分别保存x=10和x=20)。

表(Table)

表的本质与基本概念

  • 万能容器:表是Lua唯一的复合类型,可实现数组、字典、类、结构体等所有复杂数据结构。
  • 动态特性:无固定结构,运行时可任意增删改查键值对。

数组功能(顺序索引)

功能 语法/示例 注意事项
声明 t = {1, 2, "a", nil, true} 索引默认从1开始,nil会影响#计算长度
取值 print(t[1]) → 1
print(t[5]) → true
索引从1开始,0或负数索引需显式声明(如t[0] = "zero"
长度 print(#t) → 4(若t = {1, nil, 3}#t=1,因遇nil提前终止) #操作符不可靠,仅适用于连续非nil元素的数组
遍历 for i=1, #t do print(t[i]) end 推荐用ipairs替代(见迭代器部分)

字典功能(自定义索引)

功能 语法/示例 注意事项
声明 t = {["name"]="Lua", age=5.4, [100]="key"} 键可以是任意类型(除nil),值可为任意类型
取值 print(t.name) → "Lua".
print(t["age"]) → 5.4
数字键不能用.访问(需[]),如t[100]
修改/新增 t.name = "LuaNew".
t["version"] = 5.4
直接赋值,不存在的键自动创建
删除 t.name = nil.
t["version"] = nil
赋值nil即可删除键值对
遍历 for k, v in pairs(t) do print(k, v) end 必须用pairs遍历所有键值对

迭代器遍历对比

迭代器 语法 遍历范围 特点
ipairs for i,v in ipairs(t) 仅顺序索引(1,2,3…) nil终止,无法遍历自定义索引
pairs for k,v in pairs(t) 所有索引(含负数、字符串等) 遍历所有键值对,无顺序保证

类与结构体实现(基于表)

功能 语法/示例 核心逻辑
类声明 Student = {age=18, name="Lua"}
function Student:printInfo() print(self.age) end
表存储属性和方法,self指代实例
实例化 local stu = {}setmetatable(stu, {__index=Student}) 通过元表__index继承类属性和方法
方法调用 stu:printInfo()(等价于Student.printInfo(stu) :调用自动传递self参数

表的公共方法(table.xxx

方法名 语法/示例 说明
insert table.insert(t, 3, "new")(在索引3插入元素)
table.insert(t, "new")(追加到末尾)
插入元素,自动调整后续索引
remove table.remove(t, 2)(删除索引2的元素,返回其值)
table.remove(t)(删除末尾元素)
删除元素,后续元素前移
sort table.sort(t)(升序)
table.sort(t, function(a,b) return a>b end)(降序)
排序数组(仅适用于顺序索引表)
concat table.concat(t, ",")(用逗号拼接元素)
table.concat(t)(无分隔符)
拼接表元素为字符串

多脚本与模块系统

变量作用域:全局与局部

类型 声明方式 作用域 示例代码 注意事项
全局变量 直接赋值(无local 所有脚本可见 a = 10
for i=1,2 do c="test" end
print(c)(输出”test”)
过多使用消耗内存,污染全局环境
局部变量 local 变量=值 仅当前作用域可见 for i=1,2 do local d="test" end
print(d)(输出nil
函数内建议用local声明,避免全局污染

模块加载机制:require

阶段 语法/逻辑 示例代码 核心说明
声明模块 脚本末尾return 导出表 -- module.lua
local M={}
M.fun=function() print("hi") end
return M
导出表需包含对外接口,避免直接暴露内部变量
加载模块 local mod = require("模块名") local utils = require("utils")
utils.fun()(调用模块方法)
首次加载执行脚本,缓存结果;重复加载直接返回缓存
返回值处理 接收require的返回值 local localVar = require("script")
print(localVar)(获取脚本返回的局部变量)
脚本可通过return暴露局部变量,外部仅能访问返回值

脚本卸载与重新加载

操作 语法/逻辑 示例代码 应用场景
卸载脚本 package.loaded["模块名"] = nil package.loaded["LuaMultiScriptTest"] = nil
require("LuaMultiScriptTest")(重新执行脚本)
修改脚本后强制重新加载,测试时常用
检查加载状态 package.loaded["模块名"] print(package.loaded["utils"])(返回true或模块导出值) 判断模块是否已加载,避免重复操作

全局变量存储:_G

  • 本质_G是存储所有全局变量的表,等同于全局作用域。
  • 遍历示例
    for k, v in pairs(_G) do
        print(k, type(v)) -- 输出全局变量名和类型
    end
    
  • 注意
    • 局部变量(local声明)不在_G中,如local a=1_G.anil
    • 全局变量可通过_G.变量名或直接变量名访问(等价)。

特殊语法

多变量赋值

  • 规则:变量与值左对齐匹配,值不足补nil,值过多忽略。
  • 示例
    a,b,c = 1,2       -- a=1, b=2, c=nil  
    x,y = y,x          -- 交换变量值  
    

多返回值函数

  • 特性:函数可返回多个值,接收时按顺序匹配。
  • 示例
    function f() return 1,2,3 end  
    a,b = f()        -- a=1, b=2(忽略3)  
    

逻辑运算符(and/or)

  • 短路规则
    • and:左假返左,左真返右。
    • or:左真返左,左假返右。
  • 非布尔值:除nil/false外均为真,如1 and 2 → 2nil or 3 → 3

模拟三目运算符

  • 语法(条件) and 真结果 or 假结果
  • 示例
    local max = (x>y) and x or y  -- 等价于x>y?x:y  
    

协同程序(Coroutine)

协程创建与执行对比

类型 创建方法 返回类型 执行方式 特点
线程型 coroutine.create(func) thread coroutine.resume(co) 返回首个值为执行状态(true/false),适合分步控制
函数型 coroutine.wrap(func) function co() 直接返回函数结果,无状态返回,使用更简洁

挂起与分步执行

类型 挂起方法 执行结果 示例代码
线程型 coroutine.yield(...) resume(co) 返回 {true, v1, v2...} co = create(func); resum(co)
函数型 coroutine.yield(...) co() 返回 v1, v2... co = wrap(func); print(co())

协程状态

状态 含义 检查方法 示例场景
dead 执行完毕/未启动 coroutine.status(co) yield的协程执行后
suspended 挂起(可继续执行) coroutine.status(co) 执行yield
running 正在执行 函数内coroutine.status() 协程函数执行中

核心API

方法 作用 示例
coroutine.running() 获取当前协程线程号 函数内print(coroutine.running())
coroutine.yield(...) 挂起协程并返回值 coroutine.yield(1, "ok")

元表(Metatable)

元表基础概念

  • 关系:元表是表的“父表”,子表执行特定操作时会调用元表中的元方法。
  • 设置/获取
    • setmetatable(子表, 元表):设置元表。
    • getmetatable(子表):获取元表。

核心元方法对比

元方法 触发条件 参数 示例代码 说明
__tostring 子表被转为字符串(如print function(子表) print(子表) → 调用元表的__tostring返回值 自定义表的字符串表示
__call 子表被当作函数调用 function(子表, arg1, arg2...) 子表(1, 2) → 执行元表的__call 使表可像函数一样调用
__add 子表参与+运算 function(表1, 表2) 表1 + 表2 → 调用__add返回和 重载加法运算符
__index 子表查找属性不存在时 - 表/函数:function(子表, 键)
- 表:{键=值}
子表.属性 → 查元表__index
__index=function(t,k) return t[k] or "默认值"
属性查找的fallback机制,可设为函数或表
__newindex 子表赋值属性不存在时 function(子表, 键, 值) 子表.属性=值 → 调用__newindex
__newindex=function(t,k,v) t[k]=v*2 end
控制属性赋值行为,可设为函数或表

运算重载元方法

运算符 元方法 示例
+ __add a + b__add(a, b)
- __sub a - b__sub(a, b)
* __mul a * b__mul(a, b)
/ __div a / b__div(a, b)
== __eq a == b__eq(a, b)
..(拼接) __concat a .. b__concat(a, b)

rawget与rawset(绕过元方法)

函数 作用 示例代码 对比
rawget(表, 键) 直接获取表属性,不触发__index rawget(t, "key") → 直接查t["key"] 表.键的区别:后者会触发__index
rawset(表, 键, 值) 直接设置表属性,不触发__newindex rawset(t, "key", 10) → 直接设t["key"]=10 表.键=值的区别:后者会触发__newindex

面向对象(模拟)

核心实现基础

概念 实现方式 关键代码示例
类定义 表 + 元表 Object = {}(万物之父类)
实例化 :new() 方法返回表对象,设置元表 function Object:new() local obj={}; setmetatable(obj,self); self.__index = self end
属性/方法 表字段存储属性,函数存储方法 Object.id=1; function Object:PrintId() print(self.id) end

封装:万物之父类设计

功能 实现逻辑 示例效果
实例化 新建表,设元表为当前类,__index指向自身 local obj = Object:new()obj是表,元表为Object
属性访问 实例无属性时查元表__index obj.id → 查Object.id(默认1),实例赋值后覆盖
方法调用 方法存储在类表,self传实例 obj:PrintId() → 调用Object.PrintId(obj)

继承:子类注册机制

步骤 核心代码 继承链
注册子类 Object:subClass("Person") → 在_G中创建子类表 _G.Person = {}; setmetatable(Person, Object)
属性继承 子类无属性时查父类元表__index person1.id → 查Person.__indexObject.id
方法继承 子类方法不存在时查父类 person1:PrintId() → 调用Object.PrintId

多态:方法重写

场景 父类方法 子类重写
移动方法 GameObject:Move() → 坐标+1 Player:Move() → 调用父类逻辑后扩展
调用父类 self.base.Move(self)(显式传实例) Player:Move() 中通过base调用父类
多态表现 同一方法名,不同实现 player1:Move()gameObj:Move() 行为不同

关键代码模板

  1. 类定义与实例化

     Object = {}
     setmetatable(Object, Object)
     Object.__index = Object
    
     function Object:new()
         local obj = {}
         setmetatable(obj, self)
         self.__index = self
         return obj
     end
    
  2. 子类继承

     function Object:subClass(className)
         _G[className] = {}
         _G[className].base = self
         setmetatable(_G[className], self)
         self.__index = self
     end
    
  3. 多态方法重写

    -- 父类方法  
    function GameObject:Move() 
       self.posX+=1; print(self.posX) 
    end  
    -- 子类重写  
    function Player:Move() 
       self.base.Move(self); 
       print("Player移动") 
    end  
    

自带库

时间库(os

函数 说明 参数 返回值 示例
time() 获取当前时间戳(秒) 数值(时间戳) print(os.time())1695818702
time({year, month, day}) 生成指定日期的时间戳 表(year, month, day等) 数值(时间戳) os.time({year=2014, month=8, day=14})1407988800
date("*t") 获取当前时间的表对象 格式字符串"*t" 表(含year, month, day等字段) local t=os.date("*t"); print(t.year)2023

数学库(math

函数 说明 参数 返回值 示例
abs(x) 取绝对值 数值x 数值(绝对值) math.abs(-1)1
deg(x) 弧度转角度 弧度值x 角度值 math.deg(math.pi)180
cos(x) 余弦函数(弧度) 弧度值x 余弦值 math.cos(math.pi)-1
floor(x) 向下取整 数值x 整数(≤x的最大整数) math.floor(2.6)2
ceil(x) 向上取整 数值x 整数(≥x的最小整数) math.ceil(5.2)6
max(a, b) 取最大值 数值a, b 较大值 math.max(1, 2)2
min(a, b) 取最小值 数值a, b 较小值 math.min(4, 5)4
modf(x) 分离整数和小数部分 数值x 整数部分和小数部分 math.modf(1.2)1, 0.2
pow(x, y) 幂运算(x^y) 底数x, 指数y 数值(x的y次方) math.pow(2, 5)32
randomseed(seed) 设置随机数种子 种子值seed math.randomseed(os.time())
random(n) 生成[1, n]的随机整数 最大值n 随机整数 math.random(100)20(示例值)
sqrt(x) 开平方 数值x 平方根 math.sqrt(4)2

路径库(package.path

功能 说明 操作方式 示例
查看加载路径 获取Lua脚本搜索路径 print(package.path) 输出路径字符串(如;.\?.lua;...
修改加载路径 拼接新路径(如添加自定义目录) package.path = package.path .. ";C:\\" 路径末尾添加;C:\

垃圾回收(GC)

  • Lua 垃圾回收从 5.1 的增量式三色标记-清除演进到 5.4 的分代式回收,既兼顾低停顿,又优化短命对象的处理。
  • 三色标记机制将对象分为白、灰、黑三种状态:
    • 白色:未检查、潜在垃圾;
    • 灰色:已可达、待扫描其子引用;
    • 黑色:自身及子对象均已扫描、安全保留。
  • 分代回收将“新生代”对象高频回收、“老生代”对象低频处理,通过晋升策略减少对长寿对象的重复扫描。
  • 弱引用表(__mode="k"|"v"|"kv")让缓存或循环引用的一端不阻止 GC,自动释放不再需要的对象,避免内存泄漏。
  • 终结器(__gc 元方法)为 userdata 提供析构式清理入口,在对象回收前执行自定义逻辑,确保外部资源(文件、网络、C 内存等)得到正确释放。
  • collectgarbage() 系列接口允许:
    • 统计内存("count")、触发完整回收("collect");
    • 调节回收阈值("setpause")与步长("setstepmul");
    • 手动步进("step")、暂停/重启 GC("stop"/"restart")。
  • 在性能敏感场景(如游戏关键帧或热更新逻辑)建议结合手动触发以控制停顿。

深拷贝

浅拷贝与深拷贝对比

类型 浅拷贝(=) 深拷贝(自定义实现)
基本类型(string/number/boolean) 复制值,新变量与原变量独立 同浅拷贝(无需额外处理)
表(table) 复制引用,修改新表会影响原表 递归复制所有层级表,新表与原表完全独立
示例 tbl2=tbl1; tbl2.x=4 → tbl1.x=4 tbl2=clone(tbl1); tbl2.x=4 → tbl1.x=1

深拷贝实现核心逻辑

步骤 代码要点 说明
1. 缓存表防循环引用 local lookup_table = {} 记录已拷贝的表,避免循环引用导致无限递归
2. 处理基本类型 if type(object)~="table" then return object 非表类型直接返回(值复制)
3. 处理已拷贝表 elseif lookup_table[object] then return lookup_table[object] 若表已拷贝,直接返回缓存的新表
4. 递归拷贝表 new_table[_copy(key)] = _copy(value) 对表的每个键值对递归调用拷贝函数,键和值都可能是表
5. 保留元表 setmetatable(new_table, getmetatable(object)) 新表继承原表的元表,保持行为一致性

核心代码示例

function clone(obj)
    local lookup = {}
    local function _copy(o)
        if type(o) ~= "table" then return o end
        if lookup[o] then return lookup[o] end
        local new = {}
        lookup[o] = new
        for k, v in pairs(o) do
            new[_copy(k)] = _copy(v)
        end
        return setmetatable(new, getmetatable(o))
    end
    return _copy(obj)
end


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏