Lua脚本详解
一、两种交互模式
1.1 命令行模式
直接在命令行中输入语句,回车即可看到运行结果;
在任意目录下使用lua命令进入lua命令行模式,在其中输入语句后回车即可运行显示出结果,使用ctrl+C退出模式;lua对语句后的分号要求不是强制性的,有没有都行;
1.2 脚本文件模式
先要编写脚本文件,然后再使用 lua 命令来运行文件;
例如,在当前用户主目录中新建一个 script 目录,在其中创建一个名称为 hello.lua 的文件,文件中就写一名 print()语句即可;
print("hello lua");
二、脚本运行方式
2.1 lua命令方式
2.2 可执行文件方式
1. 修改脚本文件内容,表示当前文件将使用 /usr/bin/lua 命令来运行;
#!/usr/bin/lua
print("hello lua");
2. 修改脚本文件权限
# 为脚本文件赋予可执行权限
chmod 755 hello.lua
3. 运行
三、Lua语法基础
3.1 注释
Lua 的行注释为两个连续的减号,段注释以 --[[ 开头,以 ]]-- 结尾;
3.2 数据类型
Lua 中有 8 种类型,分别为:nil、boolean、number、string、userdata、function、thread 和 table。通过 type()函数可以查看一个数据的类型,例如,type(nil)的结果为 nil,type(123)的结果为 number。
3.3 标识符
程序设计语言中的标识符主要包含保留字、变量、常量、方法名、函数名、类名等。
Lua 的标识符由字母、数字与下划线组成,但不能以数字开头。Lua 是大小写敏感的;
3.3.1 保留字
3.3.2 变量
Lua 是弱类型语言,变量无需类型声明即可直接使用。变量分为全局变量与局部变量。
Lua 中的变量默认都是全局变量,即使声明在语句块或函数里。全局变量一旦声明,在当前文件中的任何地方都可访问。局部变量 local 相当于 Java 中的 private 变量,只能在声明的语句块中使用。
3.3.3 动态类型
Lua 是动态类型语言,变量的类型可以随时改变,无需声明。
3.4 运算符
3.5 函数
Lua 中函数的定义是以 function 开头,后跟 函数名 与参数列表,以 end 结尾。其可以没有返回值,也可以一次返回多个值。
3.5.1 固定参函数
其不要求实参的个数必须与函数中形参的个数相同。如果实参个数少于形参个数,则系统自动使用 nil 填充;如果实参个数多于形参个数,多出的将被系统自动忽略。
# 定义一个普通函数,包含两个形参
function f(a, b)print(a, b)
endf() # nil nilf(10) # 10 nilf(10, 20) # 10 20f(10, 20, 30) # 10 20
# 定义一个普通函数,包含两个形参
function f(a, b)print(a, b, c)
endf() # nil nil nilf(10) # 10 nil nilf(10, 20) # 10 20 nilf(10, 20, 30) # 10 20 nil
3.5.2 可变参函数
在函数定义时不给出具体形参的个数,而是使用三个连续的点号。
在函数调用时就可以向该函数传递任意个数的参数,函数可以全部接收。
# 定义一个普通函数,包含可变参数
function f(...)local a,b,c,d=...print(a, b, c, d)print(...)
endf(10, 20, 30) # 10 20 30 nil
# 10 20 30f(10, 20, 30, 40) # 10 20 30 40
# 10 20 30 40f(10, 20, 30, 40, 50) # 10 20 30 40
# 10 20 30 40 50
3.5.3 可返回多个值
Lua 中的函数一次可以返回多个值,但需要有多个变量来同时接收。
function f(a, b)local sum = a + blocal mul = a * breturn sum mul;
end# 一次性接收两个值
m, n = f(3, 5)
print(m, n) # 8 15
3.5.4 函数作为参数
Lua 的函数中,允许函数作为参数。而作为参数的函数,可以是已经定义好的普通函数,也可以是匿名函数。
function sum(a, b)return a + b
endfunction mul(a, b)return a * b
endfunction f(m, n, fun)local result = fun(m, n)print(result)
endf(3, 5, sum) # 8
f(3, 5, mul) # 15# 匿名函数调用
f(3, 5, function(a, b)return a - b;end
); # -2
3.6 流程控制语句
a = 5
if a>0 thenprint("num > 0")
elseif num == 0 thenprint("num = 0")
elseprint("num < 0")
enda = 3
while a>0 doprint(a)a = a - 1
endrepeatprint(a)a = a - 1
until a <= 0# i是执行的循环变量
# 10 为循环起始值
# 50 为循环结束值
# 20 为循环步长
for i=10, 50, 20 doprint(i)
end
# 10 30 50
for i=1, 9 doprint(i)if i == 5 thenbreakend
end
# 1 2 3 4 5
3.6.1 goto语句
funtion f(a)::flag::print("==========")if a>1 thenprint(a)a = a - 1goto flagend
endf(5)
goto 语句可以将执行流程无条件地跳转到指定的标记语句处开始执行,注意,是开始执行,并非仅执行这一句,而是从这句开始后面的语句都会重新执行。当然,该标识语句在第一次经过时也是会执行的,并非是必须由 goto 跳转时才执行。
四、语法进阶
4.1 table
4.1.1 数组
使用 table 可以定义一维、二维、多维数组。不过,需要注意,Lua 中的数组索引是从 1 开始的,且无需声明数组长度,可以随时增加元素。当然,同一数组中的元素可以是任意类型。
4.1.2 map
使用 table 也可以定义出类似 map 的 key-value 数据结构。其可以定义 table 时直接指定 key-value,也可单独指定 key-value。而访问时,一般都是通过 table 的 key 直接访问,也可以数组索引方式来访问,此时的 key 即为索引。
4.1.3 混合结构
Lua 允许将数组与 key-value 混合在同一个 table 中进行定义。key-value 不会占用数组的数字索引值。
4.1.4 table操作函数
Lua中提供了对table进行操作的函数;
table.concat()
# 该函数用于将指定的 table 数组元素进行字符串连接。
# 连接从 start 索引位置到 end 索引位置的所有数组元素, 元素间使用指定的分隔符 sep 隔开。
# 这个连接与 key-value 无关,仅是连接数组元素;table.unpack()
# 该函数返回指定 table 的数组中的从第 i 个元素到第 j 个元素值
# i 与 j 是可选的,默认 i 为 1,j 为数组的最后一个元素table.pack()
# 该函数的参数是一个可变参,其可将指定的参数打包为一个 table 返回
# 返回的 table 中具有一个属性 n,用于表示该 table 包含的元素个数table.maxn()
# 该函数返回指定 table 的数组中的最大索引值,即数组包含元素的个数table.insert()
# 该函数用于在指定 table 的数组部分指定位置 pos 插入值为 value 的一个元素
# 其后的元素会被后移。pos 参数可选,默认为数组部分末尾;table.remove()
# 该函数用于删除并返回指定 table 中数组部分位于 pos 位置的元素
# 其后的元素会被前移。pos 参数可选,默认删除数组中的最后一个元素table.sort()
# 该函数用于对指定的 table 的数组元素进行升序排序
# 也可按照指定函数 fun(a,b) 中指定的规则进行排序
# fun(a,b)是一个用于比较 a 与 b 的函数,a 与 b 分别代表数组中的两个相邻元素# 如果 arr 中的元素既有字符串又有数值型,那么对其进行排序会报错
# 如果数组中多个元素相同,则其相同的多个元素的排序结果不确定,即这些元素的索引谁排前谁排后,不确定
# 如果数组元素中包含 nil,则排序会报错
4.2 迭代器
Lua 提供了两个迭代器 pairs(table)与 ipairs(table)。这两个迭代器通常会应用于泛型 for 循环中,用于遍历指定的 table。
ipars(table):仅会迭代指定 table 中的数组元素;
pairs(table):迭代整个 table 元素,无论是数组元素,还是 key-value;
4.3 模块
模块是 Lua中特有的一种数据结构。Lua加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以API接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度;
模块文件主要由table组成,在table中添加相应的变量、函数,最后文件返回该table即可。只需要通过require将该模块导入即可;
4.3.1 定义一个模块
模块是一个 lua 文件,其中会包含一个 table。
4.3.2 使用模块
require(“文件路径”),其中文件名是不能写.lua 扩展名的。
该函数可以将指定的 lua 文件静态导入(合并为一个文件);
该函数的使用可以省略小括号,写为 require “文件路径”。
require()函数是有返回值的,返回的就是模块文件最后 return 的 table。可以使用一个变量接收该 table 值作为模块的别名;
4.3.3 再看模块
模块文件中一般定义的变量与函数都是模块 table 相关内容,但也可以定义其它与 table 无关的内容。
这些全局变量与函数就是普通的全局变量与函数,与模块无关,但会随着模块的导入而同时导入。
4.4 元表与元方法
元表,即 Lua 中普通 table 的元数据表,而元方法则是元表中定义的普通表的默认行为。
Lua 中的每个普通 table 都可为其定义一个元表,用于扩展该普通 table 的行为功能。
4.4.1 重要函数
setmetatable(table, metatable):将metatable指定为普通表table的元表;getmetatable(table):获取指定普通表 table 的元表;
4.4.2 __index元方法
当用户在对 table 进行读取访问时,如果访问的数组索引或 key 不存在,那么系统就会自动调用元表的_ _index 元方法。
该重写的方法可以是一个函数,也可以是另一个表。
如果重写的_ _index 元方法是函数,且有返回值,则直接返回;如果没有返回值,则返回 nil。
nil
通过【x】访问的值不存在
通过【2】访问的值不存在
depart
西安
4.4.3 __newindex元方法
当用户为 table 中一个不存在的索引或 key 赋值时,就会自动调用元表的_ _newindex 元方法。该重写的方法可以是一个函数,也可以是另一个表。如果重写的_ _newindex 元方法是函数,且有返回值,则直接返回;如果没有返回值,则返回 nil。
depart新增的key为x value为天津天津
nil
天津# emp.x = "天津"触发元表的__newindex,数据存入other表,故print(other.x)输出天津
4.4.4 运算符元方法
如果要为一个表扩展加号(+)、减号(-)、等于(==)、小于(<)等运算功能,则可重写相应的元方法。
例如,如果要为一个 table 扩展加号(+)运算功能,则可重写该 table 元表的_ _add 元方法,而具体的运算规则,则是定义在该重写的元方法中的。这样,当一个 table 在进行加法(+)运算时,就会自动调用其元表的_ _add 元方法。
1 北京5
2 张三5
3 28
4 上海5
5 销售部5
6 广州5
7 17
8 深圳5
name 张三5
age 28
depart 销售部5
4.4.5 __toString元方法
直接输出一个 table,其输出的内容为类型与 table 的存放地址。
如果想让其输出 table 中的内容,可重写_ _tostring 元方法。
1:北京 name:张三 age:23 2:上海 depart:销售部 3:广州 4:12 5:深圳
1:北京5 name:张三5 age:28 2:上海5 depart:销售部5 3:广州5 4:17 5:深圳5
4.4.6 __call元方法
当将一个 table 以函数形式来使用时,系统会自动调用重写的_ _call 元方法。
该用法主要是可以简化对 table 的相关操作,将对 table 的操作与函数直接相结合。
1 北京hello
2 张三hello
3 28
4 上海hello
5 销售部hello
6 64
7 广州hello
8 深圳hello
name 张三hello
age 28
depart 销售部hello
4.4.7 元表单独定义
为了便于管理与复用,可以将元素单独定义为一个文件。
该文件中仅可定义一个元表,且一般文件名与元表名称相同。
若一个文件要使用其它文件中定义的元表,只需使用 require “元表文件名”即可将元表导入使用。
4.5 面向对象
Lua 中没有类的概念,但通过 table、function 与元表可以模拟和构造出具有类这样功能的结构。
4.5.1 简单对象的创建
Lua 中通过 table 与 fcuntion 可以创建出一个简单的 Lua 对象:table 为 Lua 对象赋予属性,通过 function 为 Lua 对象赋予行为,即方法。
4.5.2 类的创建
Lua 中使用 table、function 与元表可以定义出类:使用一个表作为基础类,使用一个 function 作为该基础类的 new()方法。在该 new()方法中创建一个空表,再为该空表指定一个元表。
该元表重写_ _index 元方法,且将基础表指定为重写的_ _index 元方法。由于 new() 中的表是空表,所以用户访问的所有 key 都会从基础类(表)中查找。
带参构造器
4.6 协同线程与协同函数
Lua 中有一种特殊的线程,称为 coroutine,协同线程,简称协程。其可以在运行时暂停执行,然后转去执行其它线程,然后还可返回再继续执行没有执行完毕的内容。即可以“走走停停,停停再走走”。
协同线程也称为协作多线程,在 Lua 中表示独立的执行线程。任意时刻只会有一个协程执行,而不会出现多个协程同时执行的情况。
协同线程的类型为 thread,其启动、暂停、重启等,都需要通过函数来控制。下表是用于控制协同线程的基本方法。
4.6.1 协同函数
协同线程可以单独创建执行,也可以通过协同函数的调用启动执行。
使用 coroutine 的 wrap()函数创建的就是协同函数,其类型为 function。
由于协同函数的本质就是函数,所以协同函数的调用方式就是标准的函数调用方式。
只不过,协同函数的调用会启动其内置的协同线程。
-- 创建一个协同函数
cf = coroutine.wrap(function (a, b)print(a, b)-- 获取当前协同函数创建的协同线程tr = coroutine.running()print("tr的类型为:" .. type(tr))-- 挂起当前的协同线程coroutine.yield(a+1, b+1)print("又重新返回到了协同线程")return a+b, a*bend
)-- 调用协同函数,启动协同线程
result1, result2 = cf(3, 5)
print(result1, result2)
# 3 5
# tr的类型为: thread
# 4 6print("cf的类型为:" .. type(cf)) # cf的类型为: function-- 重启挂起的协同线程
result1, result2 = cf(3, 5)
print(result1, result2)
# 又重新返回到了协同线程
# 8 15
4.7 文件IO
Lua 中提供了大量对文件进行 IO 操作的函数。这些函数分为两类:静态函数与实例函数。
所谓静态函数是指通过 io.xxx()方式对文件进行操作的函数,而实例函数则是通过 Lua 中面向对象方式操作的函数。
4.7.1 常用IO静态函数
4.7.2 常用实例函数
file.seek()