Lua入门(更新中)

一,前情提要
本文章是我将近期学习的lua语法整理并分享。主要是复习用,也可以学习入门,由于我不想从0开始解释什么是变量,什么是函数,所以阅读本文章,可能需要有一定编程基础。
这篇文章可能会有纰漏,欢迎大家批评指出。目前博客没有开放评论区,麻烦发送邮箱至 jinzi0619@163.com 。或者在”https://github.com/xingchen-jin/Firefly” 。底下留言,本人实在是太懒狗了,喵。
二,HelloWorld
让我们开始吧,首先是学习编程的固定节目,输出”你好,世界”,大家可以先看看下面的代码
str = "你好,世界!"print("Hello, World!"); -- Hello, World!print(str) -- 你好,世界!不知道大家看出来没有,代码有奇怪的地方。只有第二行有”;“。没错,但是这样写是合法的,对于lua,一条语句末尾可以选择加不加”;“。这都是没有问题的。但是,我还是推荐统一点比较好。我是选择不加分号的。
第一行 str = "你好,世界!"; 这是在给str变量赋值。对于lua,不需要在语句开头前严格声明变量类型。
- 注:如果中文字符串输出乱码,这大概率是系统终端不支持UTF-8编码导致
- 解决方法1: 把编码格式改为GB2312,但是可能造成他人协作上的不便。
- 解决方法2:将Windows系统的默认编码调整为UTF-8。但可能影响某些旧版软件的运行和使用。
三,lua数据类型
在lua中,有以下数据类型:
- nil : 表示无效值(在条件表达式相当于false)
- boolean:布尔值
- number:表示双精度类型的实浮点数,所有的数值都是number
- string:字符串,用双引号或单引号表示。lua里 没有char 复杂类型:
- function:函数
- userdata:表示任意存储在变量中的C数据结构
- thread:表示独立线程,用于执行协同程序
- table:表
四,注释
-- 单行注释
--[[多行注释]]--五,运算符
算术运算符
有 + - * / % ^ // 没有自增自减 ++ —
^符号可能有点陌生,它是乘幂运算符,比如5 ^ 2 结果是25
//符号是整除运算符,5//2 输出结果 2
-号如果单独放在变量/常量前,代表负号,比如 -A就是A的相反数
条件运算符
> < >= <= == ~=
逻辑运算符
and or not
lua中 也遵循逻辑运算的 “短路” 规则
print( true and false) -- falseprint( true and true) -- trueprint( false and true) -- false,不进行and后的判断,直接返回结果print( true or false) -- true ,不进行or后的判断,直接返回结果print( false or false) -- falseprint( not true) -- false六,字符串相关
获取长度
print(#s)输出字符串s的长度,一个汉字占3个长度,一个英文字符 占1个长度
- 注: 本质上#是来获取字节序列长度的,所以对于汉字,可能在不同编码下所占长度不同。比如最常见的UTF-8编码是3字节,而GB2312编码是2字节。
- 因为这个特性,#来获取长度,可能造成不便。
字符串多行打印
--lua中也是支持转义字符的
print("游戏是\n第九艺术") --游戏是 --第九艺术
s = [[今天天气真好呀我们去散步吧]]print(s) --今天 --天气 --真好呀 --我们去散步吧字符串拼接
字符串拼接 通过“ ..”
print( "123" .. "456" )输出123456
也可以拼接number
同时,lua字符串还有个函数**string.format()**用于格式化字符串。
比如
print(string.format("再见,%d", 2026)) -- 再见,2026--%d :与数字拼接--%a:与任何字符拼接--%s:与字符配对转字符串
使用tostring()
字符串提供的一些公共方法
str = "abCdefgCd"--小写转大写的方法print(string.upper(str))--大写转小写print(string.lower(str))--翻转字符串print(string.reverse(str))--字符串索引查找print(string.find(str, "Cde"))--截取字符串print(string.sub(str, 3, 4))--字符串重复print(string.rep(str, 2))--字符串修改print(string.gsub(str, "Cd", "**"))
--字符转 ASCII码a = string.byte("Lua", 1)print(a)--ASCII码 转字符print(string.char(a))条件分支
lua的条件分支逻辑和主流的高级语言区别不大,记住格式即可。
- 注:lua中没有switch语法
单分支
格式为,条件为true时进入分支
if 条件 then 分支end多分支
多分支在单分支基础上,多了elseif 和else
if 条件 then 分支1elseif 条件 then 分支2elseif 条件 then 分支3else 分支end当所有都不满足时进入else
循环
for循环
for循环,包含结尾
for i =2,5 do --默认递增 i会默认+1,从2输出到5 print(i)end
for i =1,5,2 do --如果要自定义增量 直接逗号后面写 print(i)end
for i =5,1,-1 do --如果要自定义增量 直接逗号后面写 print(i)endwhile循环
while num < 5 do --满足条件进入循环体 print(num) num = num + 1endrepeat(do - while)最特殊
格式为
repeat 循环体until 条件--例子repeat print(num) num = num + 1until num > 5 --满足条件跳出 结束条件必定循环一次,满足条件则退出
函数(function)
在lua中,函数和数字,字符串一样,是一个数据类型,一种值。
函数的作用是让代码方便复用的。在使用上可以把一个常用的程序段作为函数,用到的时候调用。
现在让我们看看lua中函数长什么样吧。
无参无返回
function F1() print("无参无返回函数")endprint(type(F1)) --functionF1() --调用函数,输出无参无返回函数函数类型是function。
而上面的格式其实是lua的语法糖,这种格式更接近主流使用的高级语言风格。
现在让我们看看第二种格式。
F2 = function() print("无参无返回函数)endF2()第二种格式有点类似C#中的委托和事件,将匿名函数作为变量存起来。
函数是一种变量,第一种格式其实会被翻译成F1 = function() ... end
还有一点需要注意的是,尽管我们两种方式的输出结果一致。但这两种声明方式,却并非等价。请保持耐心,先让我讲讲其他内容,再继续说明。
有参有返回
function F3(a) return a, "123", trueendtemp, temp2, temp3, temp4 = F3("1")print(temp) --1print(temp2) --123print(temp3) --trueprint(temp4) --nillua函数可以有多个返回值,我们可以用多个变量,去接收。
- 当接收数量大于返回数量,多的会直接复制nil 。
- 当接收数量小于返回数量,缺失的部分会直接舍弃。
变长参数
function F4( ... ) --变长参数使用 用一个表存起来 arg = {...} --遍历表 for i=1,#arg do print(arg[i]) endendF4(1,"123",true,4,5,6) --依次打印传入参数我们还没有讲到表,不过这里可以看作一个数组。
函数嵌套与闭包
我们可以通过一个函数返回一个函数的方式实现函数嵌套。
function F5() return function() print(123); endendf6 = F5()f6() --123上面的步骤是这样的:首先 F5 会返回一个匿名函数,我们用变量 f6 接收,然后再调用这个返回的函数,最后打印 123。
而闭包是在这个基础上,利用了“延长外部变量生命周期”的机制。
我们现在听到闭包,可能会一头雾水,其实不用害怕,闭包其实并不复杂,我们先来看一个例子:
function F6(x) -- 延长传入参数的生命周期 return function(y) return x + y endendf7 = F6(10)print(f7(5)) -- 15我们在调用 F6 时传入了参数 10,返回的函数捕获(记住) 了这个参数。也就是说,实际上返回的函数,内部已经绑定了当时 x = 10 的状态。所以当我们调用 f7(5) 时,相当于执行了 10 + 5。
变量生命周期
在lua中,我们默认声明的变量都是全局变量,这跟很多其他的高级语言有着显著区别,其他往往会默认私有或可继承变量。 而声明局部变量,需要在变量前加上local关键字。 举个例子,
function F8() local a = 1 b = 2endF8()print(a) --nilprint(b) --2两种函数声明方式的区别(坑点!!!)
现在我们回到讲无参函数时,提到的两种声明方式。
他们最核心的区别是,在 local 局部变量下的递归行为
写法 A:局部函数声明
i = 0
local function F1() i = i + 1 --这里为避免溢出,提供了一个终止条件 if i > 5 then return end print("F1") -- 输出 "F1" F1() -- ✅ 合法,可以正常递归调用自己endF1()原理:Lua 在解析 local function F1 时,会先声明变量 F1,然后再赋值函数体。所以在函数内部可以访问到 F1 自身。
写法 B:局部变量赋值匿名函数
i=0local F2 = function() i = i + 1 --这里为避免溢出,提供了一个终止条件 if i > 5 then return end print("F2") F2() -- ❌ 会报错:attempt to call a nil value (global 'F2')end原理:Lua 在解析 local F2 = ... 时,会先计算右侧的表达式,再赋值给左侧。在执行到 function() 内部时,外部的 F2 还是 nil,所以会报错。如果非要这么写递归,必须提前声明:
local F2i = 0F2 = function() i = i + 1 --这里为避免溢出,提供了一个终止条件 if i > 5 then return end print("F2") -- 输出 "F2" F2() -- ✅ 此时正常endF2() -- 输出 "F2" 并尝试递归调用自己- 注意:在自己实践时,最好新开一个文件,因为这些函数可能已经声明过。
表(table)
在lua中,大多数复杂类型都是table
我们可以用table表示一个数组,一个字典,甚至是一个类。
数组
一维数组
用表声明一个数组很简单
a = {99,114,nil,false,"78",nil}--lua中 索引从1开始print(a[1]) --99print(a[3]) --nilprint(a[4]) --falseprint(a[7]) --nil需要注意的是下标从1开始,超出访问边界会返回nil
print(#a) --5
”#“是通用的获取长度的关键字,但是当表中存在空洞(存在nil)时,往往不会返回我们想要的结果。
而且离谱的是,就结果而言,不同的环境可能有不同的值。对于我的环境,输出的结果是5,省略末尾的nil。而有些环境会返回2。也就是遇到nil就不计数了。
顺带一提我的情况是末尾的nil都省略。
b = {nil, nil, nil}print(#b) --0所以,最好不要通过“#”去得到数组长度
二维数组
依旧是下标从1开始。
a = {{1,2,3},{4,5,6},{7,8,9}}print(a[1][1]) --1print(a[2][1]) --4自定义索引
我们可以在为数组添加自定义索引,但是这其中有很多 “深坑”。
aa = {[0] = 1,2,3,[-1] = 4, 5}print(aa[0]) --1print(aa[-1]) --4print(aa[1]) --2print(aa[2]) --3print(aa[3]) --5没有定义的索引从1开始,所以索引1对应的是数字2,然后索引3对应数字5。 好像是把索引忽略了,对于未定义索引从1开始。 “aa = {[1] = 1,2,3,[-1] = 4, 5} — print(aa[1])输出2
接下来获取长度print("数组长度:"..#aa) --数组长度:3
让我们换一个数组
--情况1bb = {[1] =1, [2] = 2,[4] = 4, [5] = 5}print("数组长度:"..#bb) --数组长度:5我们明明跳过了下标3,实际上只有四个数,结果是5。
那我分别列出其他几种情况
--情况2bb = {[1] =1, [2] = 2,[5] = 5, [6] = 6}print("数组长度:"..#bb) --数组长度:2--情况3bb = {[1] =1, [2] = 2,[4] = 4, [6] = 6}print("数组长度:"..#bb) --数组长度:6for i=1,#bb do print(bb[i])end--情况4bb = {[1] =1, [2] = 2,[3] = 4, [5] = 7}print("数组长度:"..#bb) --数组长度:3- 情况2,跳了两个数,中断长度。
- 情况3,连续跳两个数,不中断读取。遍历后会发现中间空的部分是nil
- 情况4,最后跳一个属,最后一个数不记录长度。
这些深坑是lua底层实现原理造成的,理论上找出底层代码,就可以分析为什么。嘻嘻,我没找
pairs和ipairs迭代器遍历
通过上面的学习,我们了解了用#来获取长度的不可靠性。那不用#的方式,如何实现表的遍历呢,对于这种情况,lua提供了两个迭代器供我们使用。
先声明一个表
“a = {[0] = 1, 2, [-1]=3, 4, 5, [5] = 6}
ipairs
ipairs遍历,还是从1开始往后遍历的,小于等于0的值得不到. 只能找到连续索引的 键 如果中间断序了,它也无法遍历出后面的内容
for i,k in ipairs(a) do print("ipairs遍历键值"..i.."_"..k)end--[[ipairs遍历键值1_2ipairs遍历键值2_4ipairs遍历键值3_5]]for i in ipairs(a) do print("ipairs遍历键"..i)end--[[ipairs遍历键1ipairs遍历键2ipairs遍历键3]]ipairs遍历表,第一个索引变量为键(下标),第二个索引变量为值。
pairs
pairs可以把所有的键找到,遍历整张表
for i,v in pairs(a) do print("pairs遍历键值"..i.."_"..v)end--[[pairs遍历键值1_2pairs遍历键值2_4pairs遍历键值3_5pairs遍历键值0_1pairs遍历键值5_6pairs遍历键值-1_3]]for i in pairs(a) do print("pairs遍历键"..i)end--[[pairs遍历键1pairs遍历键2pairs遍历键3pairs遍历键0pairs遍历键5pairs遍历键-1]]字典
类和结构体
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!














