八、一文学会Lua脚本 —— 超详细操作演示!

数据库系列文章:

关系型数据库 — MySQL:


非关系型数据库 — Redis:

八、Lua脚本详解

8.1 Lua 简介

    Lua 是一个由标准 C 语言 开发的、开源的、可扩展的轻量级的弱类型的解释型脚本语言, 是 于 1993 年由 巴西里约热内卢天主教大学的三人研究小组使用标准 C 语言开发。

    Lua 的官网 为: https://www.lua.org/

Lua 是一门 脚本语言,和 ShellPython 是同一种类型。

用的最多的是 Unity 手游,做 热更新 方案;Nginx 也有应用。

在生产环境下,对分布式锁的高级用法,都是我们自己编写Lua脚本控制的。

8.2 Linux 系统的Lua

8.2.1 Lua 下载

    若要使用 Lua 则需要先从官网下载其源码并安装。

在这里插入图片描述

8.2.2 Lua 安装

    先将下载好的 Lua 源码上传到 Linux ,然后再进行安装。

⭐️(1)解压

    将Lua 源码解压到 /opt/apps 目录。

tar -zxvf lua-5.4.6.tar.gz -C /opt/apps/

在这里插入图片描述

    进入到 /opt/apps 下的 lua 目录可以看到编译用的 Makefile 文件 及 源码目录 src

在这里插入图片描述

⭐️(2)安装gcc

    由于 Lua 是由 C/C++ 语言编写的,所以对其进行 编译 就必须要使用相关编译器。对于 C/C++ 语言的编译器,使用最多的是 gcc

yum -y install gcc gcc-c++

在这里插入图片描述

⭐️(3)编译

    执行编译命令 make linux test

# test 测试输出版本号
make linux test

⭐️(4)安装

make install

在这里插入图片描述

    安装完毕后,可以通过 lua -v 查看版本号,与前面 make linux test 中最后显示的结果是相同的。

在这里插入图片描述

如果 lua -v 显示的还是老版本,reboot 重启一下 Linux 系统就好了。

8.2.3 Hello World

⭐️(1)两种交互模式

    Lua 为用户提供了两种交互模式:命令行模式脚本文件模式

A、命令行模式

    该模式是,直接在命令行中输入语句,回车即可看到运行结果。

在这里插入图片描述

    在任意目录下使用 lua 命令进入 lua 命令行模式,在其中输入语句后回车即可运行显示出结果。使用 Ctrl + C 退出模式。

    需要注意, lua 对语句后的 分号要求 不是 强制性的,有没有都行

B、脚本文件模式

    该模式是先要编写脚本文件,然后再使用 lua 命令运行文件。

    例如直接创建一个名称为 hello.lua 的文件,文件中就写一名 print() 语句即可。

在这里插入图片描述

    然后 直接运行lua 脚本文件” 即可看到结果。

lua hello.lua

在这里插入图片描述

⭐️(2)两种脚本运行方式

    对于脚本文件的运行有两种方式。

  • 一种是上面的 lua 命令 方式,
  • 还有一种是 可执行文件 方式。可执行文件方式是,将 lua 脚本文件直接修改为 可执行文件 运行。

    下面就使用第二种方式来运行。

A、修改脚本文件内容

    在脚本文件第一行增加 #!/usr/bin/lua ,表示当前文件将使用 /usr/bin/lua 命令来运行。

#!/usr/bin/lua

在这里插入图片描述

B、修改脚本文件权限

chmod 755 hello.lua

    为脚本文件赋予 可执行权限

C、运行

    直接使用文件名即可运行。
在这里插入图片描述

8.3 Win 系统的Lua

    这里要安装的是在 Windows 系统中 Lua 的运行环境。最常用的为 SciTE

    SciTE 是一款 Lua 脚本 测试编辑器,提供 Lua 的编辑运行环境。其官方下载地址为: https://github.com/rjpcomputing/luaforwindows/releases 。 下载完直接运行 exe 文件安装。
在这里插入图片描述

    SciTE 提供了两种运行方式:命令行窗口运行方式 与 Lua 脚本的编辑运行环境

在这里插入图片描述
在这里插入图片描述

    除了SciTE ,还有像 LuaDistLuaRocks 等。

8.4 Lua 脚本基础

8.4.1 注释

    Lua行注释 为两个连续的减号段注释--[[ 开头,以 --]] 结尾

    不过,在 调试过程 中如果想 临时取消 段注释,而直接将其标识删除,这样做其实并不好。因为有可能还需要再添加上。而段注释的写法相对较麻烦。

  • 所以, Lua 给出了一种简单处理方式:在开头的 --[[再加一个减号,即可使段注释不起作用。其实就是使两个段注释标识变为了两个行注释。
--~ 行注释,(快捷键为 Ctr + Q)
-- 行注释,(波浪号 ~ ,为快捷键自动生成的)

--[[段注释
print("Hello, Lua")
--]]

---[[取消段注释
print("Hello, Lua")
--]]
8.4.2 数据类型

    Lua 中有 8 种 类型,分别为: nilbooleannumberstringuserdatafunctionthreadtable

  • 通过 type() 函数可以查看一个数据的类型,例如, type(nil) 的结果为 niltype(123) 的结果为 number

在这里插入图片描述

-- string 演示
str1 = "中国"
str2 = '北京'
str3 = [[深圳
广州
上海]]

print(str1) 
print(str2)
print(str3)

--[[输出:
中国
北京
深圳
广州
上海
--]]
8.4.3 标识符

    程序设计语言中的标识符主要包含 保留字、变量、常量、方法名、函数名、类名 等。Lua 的标识符由 字母、数字 与 下划线 组成,但 不能以数字开头Lua大小写敏感的

⭐️(1)保留字

    Lua 常见的保留字共有 22 个。不过,除了这 22 个外, Lua 中还定义了很多的 内置全局变量 ,这些内置全局变量的一个共同特征是,以下划线开头后跟全大写字母 。所以我们在定义自己的标识符时不能与这些保留字、内置全局变量重复

在这里插入图片描述

⭐️(2)变量

    Lua 是弱类型语言变量 无需类型声明可直接使用。变量分为 全局变量局部变量。Lua 中的变量 默认都是全局变量,即使声明在语句块或函数里。全局变量一旦声明,在当前文件中的(声明后)任何地方 都可访问。局部变量 local 相当于 Java 中的 private 变量,只能在声明的语句块中使用。

-- 局部变量
local x = 3

-- 定义一个函数
function f()
	-- 全局变量
	y = 5
	-- 再定义一个局部变量
	local z = 8
	-- 访问局部变量
	print("x = "..x);
end

-- 访问函数
f(); 				 -- 输出 x = 3
-- 访问全局变量
print("y = "..y)	 -- 输出 y = 5
-- 访问局部变量
print("z = "..z)	 -- 报错,z 为局部变量

⭐️(3)动态类型

    Lua 是 动态类型语言变量的类型 可以随时改变,无需声明

y = 5
print("y = "..y)	 -- 输出 y = 5
y = "北京"
print("y = "..y)	 -- 输出 y = 北京
8.4.4 运算符

    运算符是一个特殊的符号,用于告诉解释器执行特定的 数学逻辑运算。Lua 提供了以下几种运算符类型:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 其他运算符

⭐️(1)算术运算符

    下表列出了 Lua 语言中的常用算术运算符,设定 A 的值为 10B 的值为 20

在这里插入图片描述

注意,

  • SciTE 对 Lua 支持的目前最高版本为 5.1 ,而整除运算符 // 需要在 Lua5.3 版本以上,所以当前 SciTE 中无法看到效果。
  • 命令行模式 中,直接输入变量名 回车,即相当于 print() 函数输出该变量。

⭐️(2)关系运算符

    下表列出了 Lua 语言中的常用关系运算符,设定 A 的值为 10B 的值为 20

在这里插入图片描述

⭐️(3)逻辑运算符

    注意, Lua 系统将 falsenil 作为 ,将 true非nil 作为 即使是 0 也是

    下表列出了Lua 语言中的常用 逻辑运算符,设定 A 的值为 trueB 的值为 false

在这里插入图片描述

⭐️(4)其他运算符

    下表列出了 Lua 语言中的 连接运算符计算 字符串 长度 的运算符:

在这里插入图片描述

str = "abcdefg"
print(#str) 	 -- 输出 7
8.4.5 函数

    Lua 中函数的定义是以 function 开头,后跟 函数名参数列表,以 end 结尾。其 可以没有返回值,也可以一次返回多个值

⭐️(1)固定参函数

    Lua 中的函数在调用时与 Java 语言中方法的调用是不同的,其 不要求实参的个数必须与函数中形参的个数相同

  • 如果实参个数少于形参个数,则系统自动使用 nil 填充;
  • 如果实参个数多于形参个数,多出的将被系统 自动忽略
-- 定义一个普通函数,包含两个形参
function f(a, b)
	print(a, b)
end

-- 无实参传递
f()					-- 输出:nil	nil

-- 传递一个实参
f(10)				-- 输出:10 	nil

-- 传递两个实参
f(10, 20)			-- 输出:10 	20

-- 传递三个实参
f(10, 20, 30)		-- 输出:10 	20

⭐️(2)可变参函数

    在函数定义时不给出具体形参的个数,而是使用 三个连续的点号。在函数调用时就可以向该函数传递任意个数的参数,函数可以全部接收。

-- 定义一个可变参函数
function f(...)
	local a,b,c,d = ...
	print(a, b, c, d)
	--print(...) 	-- 可以全部输出
end

-- 传递三个实参
f(10, 20, 30)					-- 输出:10 20	30	nil

-- 传递四个实参
f(10, 20, 30, 40)				-- 输出:10	20	30	40

-- 传递五个实参
f(10, 20, 30, 40, 50)			-- 输出:10	20	30	40

⭐️(3)可返回多个值

    Lua 中的函数一次可以返回多个值,但需要有多个变量来同时接收

-- 定义一个普通函数,返回两个值
function f(a, b)
	local sum = a + b
	local mul = a * b
	return sum, mul;
end

-- 一次性接收两个值
m, n = f(3, 5)
print(m, n)					-- 输出:8 15

⭐️(4)函数作为参数

    Lua 的函数中,允许 函数 作为参数。而作为参数的函数,可以是已经定义好的 普通函数,也可以是匿名函数

-- 定义两个普通函数
function sum(a, b)
	return a + b
end

function mul(a, b)
	return a * b
end

-- 定义一个函数,其参数为另一个参数
function f(m, n, fun)
	local result = fun(m, n)
	print(result)
end

-- 调用
f(3, 5, sum)					-- 输出:8
f(3, 5, mul)					-- 输出:15

-- 匿名函数调用
f(3, 5, function (a, b)
			return b - a;
		end
);								-- 输出:2
8.4.6 流程控制语句

    Lua 提供了 if 作为 流程控制语句

⭐️(1)if 语句

    Lua 提供了 if...then 用于表示条件判断,其中 if 的判断条件可以是 任意表达式。 Lua 系统将 falsenil 作为,将 true非nil 作为,即使 0 也是

a = 5
if(a > 0) then
	print("num > 0")
else
	print("num <= 0")
end

-- 输出: num > 0

    需要注意,Lua 中的 if 语句的判断条件 可以使用小括号括起来,也可以不使用

⭐️(2)if 嵌套语句

    Lua 中提供了专门的关键字 elseif 来做 if 嵌套语句注意,不能使用 elseif 两个关键字的联用形式 ,即不能使用 else if 来嵌套 if 语句。

a = 5
if(a > 0) then
	print("num > 0")
elseif a == 0 then
	print("num = 0")
else
	print("num < 0")
end
8.4.7 循环控制语句

    Lua 提供了四种循环控制语句while...do 循环、 repeat...until 循环、数值 for 循环,及 泛型 for 循环。同时, Lua 还提供了 breakgoto 两种循环流程控制语句

⭐️(1)while … do

    只要 while 中的 条件成立 就一直循环

a = 3
while a>0 do
	print(a)
	a = a - 1    -- 注意:这里没有a--
end

输出:
3
2
1

⭐️(2)repeat … until

    until 中的 条件成立了,循环就要 停止

a = 3
repeat
	print(a)
	a = a - 1    -- 注意:这里没有a--
until a <= 0

输出:
3
2
1

⭐️(3)数值 for

    这种 for 循环只参用于循环变量数值型 的情况。其语法格式为:

for var=exp1, exp2, exp3 do
	循环体
end

    var 为指定的 循环变量exp1 为 循环 起始值exp2 为 循环 结束值exp3 为 循环 步长

  • 步长可省略不写,默认1
  • 每循环一次,系统内部都会做一次当前循环变量 var 的值与 exp2 的比较,如果 var 小于等于 exp2 的值,则继续循环,否则结束循环。

例如:

for i = 10, 50, 20 do
	print(i)
end

输出:
10
30
50

⭐️(4)泛型 for

    泛型 for 用于遍历 table 中的所有值,其需要与 Lua 的 迭代器 联合使用。后面 table 学习时再详解。

⭐️(5)break

    break 语句可以提前终止循环。其只能用于循环之中。

for i = 1, 9 do
	print(i)
	if i == 3 then
		break
	end
end

输出:
1
2
3

⭐️(6)goto (不建议使用,不然可能使代码杂乱无章)

    goto 语句可以将执行流程 无条件地跳转 到指定的标记语句处开始执行,注意,是开始执行,并非仅执行这一句,而是从这句开始后面的语句都会重新执行。当然,该标识语句在第一次经过时也是会执行的,并非是必须由 goto 跳转时才执行。

    语句标记使用一对双冒号括起来,置于语句前面。goto 语句可以使用在循环之外。

function f(a)
	::flag:: print("=========")
	if a > 1 then
		print(a)
		a = a - 1
		goto flag
	end
end

f(5)

输出:
=========
5
=========
4
=========
3
=========
2
=========

    注意,Lua5.1不支持 双冒号 的语句标记。

8.5 Lua 语法进阶

8.5.1 table

⭐️(1)数组

    使用 table 可以定义 一维、二维、多维数组。不过,需要注意, Lua 中的数组索引是从 1 开始的,且 无需声明数组长度,可以随时增加元素。当然,同一数组中的 元素可以是任意类型

-- 定义一个一维数组
cities = {"北京", "上海", "广州"}
cities[4] = "深圳"

for i=1, 4 do
	print("cities["..i.."]="..cities[i])
end

输出:
cities[1]=北京
cities[2]=上海
cities[3]=广州
cities[4]=深圳

-- 声明一个二维数组
arr = {} 			-- 必须声明空数组
for i= 1, 3 do
	arr[i] = {}		-- 必须声明空数组
	for j = 1, 2 do
		arr[i][j] = i * j
		print(arr[i][j])
	end
end

输出:
1
2
2
4
3
6

⭐️(2)map

    使用 table 也可以定义出类似 mapkey-value 数据结构。其可以定义 table 时直接指定 key-value ,也可单独指定 key-value 。而访问时,一般都是通过 tablekey 直接访问,也可以数组索引方式来访问,此时的 key 即为索引

例 1 :

-- 定义一个map
emp  = {name = "张三", age = "23", depart = "销售部"}

-- 通过下标方式操作
emp["gender"] = "男"
print(emp["name"])				-- 输出:张三
print(emp["gender"])			-- 输出:男

-- 点号方式操作 (推荐)
emp.office = "2nd floor"
print(emp.age)					-- 输出:23
print(emp.office)				-- 输出:2nd floor

例 2 :

a = "xxx"
b = 3
c = 5

-- 定义一个map,其key为表达式(需要用方括号括起来)
arr = {
	["emp_"..a] = true,
	["hi"] = 123,
	[b + c] = "hello",
}

print(arr.emp_xxx)				-- 输出:true
-- print(arr.8)   -- 报错
print(arr.hi)					-- 输出:123
print(arr[8])					-- 输出:hello

⭐️(3)混合结构

    Lua 允许将数组与 key-value 混合在同一个 table 中进行定义。 key-value 不会占用数组的数字索引值

emp  = {"北京", name = "张三", "上海", age = "23", "广州", depart = "销售部", "深圳"}

print(emp[1])					-- 输出:北京
print(emp[2])					-- 输出:上海
print(emp[3])					-- 输出:广州
print(emp[4])					-- 输出:深圳

常见使用方法:

-- 定义一个数组,map 为混合结构
emp = {
	{name="张三", age=23},
	{name="李四", age=24},
	{name="王五", age=25},
	{name="赵六", age=26},
}

for i = 1, 4 do
	print(emp[i].name.." : "..emp[i].age)
end

输出:
张三 : 23
李四 : 24
王五 : 25
赵六 : 26

⭐️(4)table 操作函数

    Lua 中提供了对 table 进行操作的函数

A、table.concat()

函数table.concat (table [, sep [, start [, end]]]):
解析】该函数用于将指定的 table 数组元素 进行 字符串连接。连接从 start 索引位置到 end 索引位置的所有数组元素, 元素间使用指定的分隔符 sep 隔开。 如果 table 是一个混合结构,那么这个连接与 key-value 无关,仅是连接数组元素

emp  = {"北京", name = "张三", "上海", age = "23", "广州", depart = "销售部", "深圳"}

print(table.concat(emp, ","))  -- 输出:北京,上海,广州,深圳
print(table.concat(emp, ",", 2, 3))  -- 输出:上海,广州

B、table.unpack()

函数table.unpack (table [, i [, j]])
解析拆包。该函数返回指定 table数组 中的从第 i 个元素到第 j 个元素值。 ij 是可选的,默认 i1j 为数组的最后一个元素。 Lua5.1 不支持该函数。

arr = {"bj", "sh", "gz", "sz"}

table.unpack(arr)  			 -- 输出:bj	sh	gz	sz
table.unpack(arr, 2, 3) 	 -- 输出:sh	gz

-- 也可以使用变量接收
a, b, c, d = table.unpack(arr)

C、table.pack()

函数table. pack (...)
解析】打包。该函数的参数是一个可变参,其可将指定的参数打包为一个 table 返回。这个返回的 table 中具有一个属性 n ,用于表示该 table 包含的 元素个数。 Lua5.1 不支持该函数。

t = table.pack("apple", "banana", "peach")
table.concat(t, ",") 			-- 输出:apple,banana,peach

D、table.maxn()

函数table.maxn(table)
解析】该函数返回指定 table 的数组中的 最大索引值,即数组包含元素的个数

emp  = {"北京", name = "张三", "上海", age = "23", "广州", depart = "销售部", "深圳"}

print(table.concat(emp, ","))  -- 输出:北京,上海,广州,深圳

print(table.maxn(emp))		   -- 输出:4

E、table.insert()

函数table.insert (table, [pos,] value):
解析】该函数用于在指定 table 的数组部分指定位置 pos 插入值为 value 的一个元素 。 其后的元素会被后移 。 pos 参数可选 默认为数组部分末尾 。

cities = {"北京", "上海", "广州"}

table.insert(cities, 2, "深圳")
table.insert(cities, "天津")

print(table.concat(cities, ",")) -- 输出:北京,深圳,上海,广州,天津

F、table.remove()

函数table.remove (table [, pos])
解析】该函数用于 删除并返回 指定 table 中数组部分位于 pos 位置的元素 。 其后的元素会被前移pos 参数可选默认删除数组中的最后一个元素

cities = {"北京", "上海", "广州", "深圳", "天津"}

table.remove(cities, 2)
table.remove(cities)

print(table.concat(cities, ",")) -- 输出:北京,广州,深圳

G、table.sort()

函数table. sort(table [,fun(a,b)])
解析】该函数用于对指定的 table 的数组元素进行 默认 升序排序,也可按照指定函数 fun(a,b)指定的规则进行排序。 fun(a,b) 是一个用于比较 ab 的函数, ab 分别代表数组中的两个相邻元素。

cities = {"bj北京", "sh上海", "gz广州", "sz深圳", "tj天津"}

table.sort(cities, function(a, b)     -- 降序
						return a > b  -- 如果相邻的两个为真,保持原来的队形
				   end
)

print(table.concat(cities, ",")) -- 输出:tj天津,sz深圳,sh上海,gz广州,bj北京

注意

  • 如果 arr 中的元素既有 字符串 又有 数值型 ,那么对其进行排序会 报错
  • 如果数组中多个元素相同,则其相同的多个元素的排序结果不确定,即这些元素的索引谁排前谁排后,不确定
  • 如果数组元素中包含 nil ,则排序会 报错
8.5.2 迭代器

    Lua 提供了两个迭代器 pairs(table)ipairs(table) 。这两个迭代器通常会应用于 泛型 for 循环中,用于遍历指定的 table 。这两个迭代器的不同是:

  • ipairs(table) :仅会迭代指定 table 中的 数组元素
  • pairs(table):会迭代 整个 table 元素 ,无论是 数组元素,还是 key-value
emp  = {"北京", name = "张三", "上海", age = "23", "广州", depart = "销售部", "深圳"}

-- 遍历emp中的所有数组元素
for i, v in ipairs(emp) do
	print(i, v)
end
--[[输出:
1	北京
2	上海
3	广州
4	深圳
--]]

-- 遍历emp中的所有元素
for k, v in pairs(emp) do
	print(k, v)
end
--[[输出:
1	北京
2	上海
3	广州
4	深圳
depart	销售部
name	张三
age	23
--]]
8.5.3 模块

    模块是 Lua 中特有的一种数据结构。 从 Lua 5.1 开始, Lua 加入了标准的 模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口 的形式在其他地方调用,有利于代码的重用降低代码耦合度

    模块文件主要由 table 组成。在 table 中添加相应的变量函数,最后文件返回table 即可。如果其它文件中需要使用该模块,只需通过 require 将该 模块导入 即可。

⭐️(1)定义一个模块

    模块 是一个 lua 文件,其中会包含一个 table 。一般情况下该文件名与该 table 名称相同,但其 并不是必须的

例如: 定义rectangle模块, 创建一个rectangle.lua 文件

-- 声明一个模块
rectangle = {}

-- 为模块添加一个变量
rectangle.pi = 3.14

-- 为模块添加函数(求周长)
function rectangle.perimeter(a, b)
	return (a + b) * 2
end

-- 以匿名函数方式为模块添加一个函数(求面积)
rectangle.area = function(a, b)
	return a * b
end


-- ================= 定义与模块无关的内容===============
-- 定义一个全局变量
goldenRatio = 0.618

-- 定义一个局部函数(求圆的面积)
local function circularArea(r)
	return rectangle.pi * r * r
end

-- 定义一个全局函数(求矩形中最大圆的面积)
function maxCircularArea(a, b)
	local r = math.min(a, b)
	return circularArea(r / 2)
end

return rectangle

⭐️(2)使用模块

    这里要用到一个函数 require("文件路径")),其中文件名是 不能写 .lua 扩展名的。该函数可以将指定的 lua 文件静态导入(合并为一个文件)。不过需要注意的是,该函数的使用可以省略小括号,写为 require"文件路径"

-- 导入一个模块
require "rectangle"

-- 访问模块的属性,调用模块的函数
print(rectangle.pi)						-- 输出:3.14
print(rectangle.perimeter(3, 5))		-- 输出:16
print(rectangle.area(3, 5))				-- 输出:15

    require() 函数是有返回值的,返回的就是模块文件最后 returntable 。可以使用一个变量接收该 table作为模块的别名,就可以 使用 别名 来访问模块了。

-- 导入一个模块
rect = require "rectangle"

-- 访问模块的属性,调用模块的函数
print(rect.pi)						-- 输出:3.14
print(rect.perimeter(3, 5))			-- 输出:16
print(rect.area(3, 5))				-- 输出:15

⭐️(3)再看模块

    模块文件中一般定义的 变量函数 都是模块 table 相关内容,但也可以定义其它与 table 无关的内容。这些 全局变量与函数 就是 普通的全局变量与函数与模块无关,但会随着模块的导入而同时导入。所以在使用时可以直接使用,而无需也不能添加模块名称。

-- 导入一个模块
require "rectangle"

-- 访问模块中与模块无关的内容
print(goldenRatio)						-- 输出:0.618
print(maxCircularArea(4, 5))			-- 输出:12.56
-- print(circularArea(2))	-- 报错,局部的不能访问
8.5.4 元表和元方法

    元表,即 Lua普通 table元数据表,而 元方法 则是元表中定义的普通表的默认行为Lua 中的每个 普通 table 都可为其定义一个元表,用于 扩展普通 table行为功能。例如,

  • 对于 table数值相加的行为, Lua 中是没有定义的,但用户可通过为其指定 元表扩展这种行为
  • 再如,用户访问不存在的 table 元素, Lua 默认返回的是 nil ,但用户可能并不知道发生了什么。此时可以通过为该 table 指定元表 来扩展 该行为:给用户提示信息,并返回用户指定的值。

⭐️(1)重要函数

    元表 中有 两个重要函数

  • setmetatable(table, metatable) :将 metatable 指定为普通表 table元表
  • getmetatable(table) 获取指定普通表 table元表

⭐️(2)__index 元方法

    当用户在对 table 进行 读取 访问时,如果 访问 的数组 索引key 不存在,那么系统就会 自动调用 元表的 _ _index 元方法。该重写的方法可以是一个函数,也可以是另一个表

  • 如果重写的 _ _index 元方法是函数,且有返回值,则直接返回
  • 如果 没有返回值,则返回 nil

例1:重写的方法是一个函数

emp  = {"北京", nil, name = "张三", "上海", age = "23", "广州", depart = "销售部", "深圳"}

print(emp.x)

-- 声明一个元表
meta = {};

-- 将原始表和元表相关联
setmetatable(emp, meta)

-- 有返回值的情况
meta.__index = function(tab, key) --匿名函数
	return "通过【"..key.."】访问的值不存在"
end

--~ -- 无返回值的情况
--~ meta.__index = function(tab, key)
--~ 	print("通过【"..key.."】访问的值不存在")
--~ end

print(emp.x)
print(emp[2])

输出
nil
通过【x】访问的值不存在
通过【2】访问的值不存在

例2:重写的方法是一个

emp  = {"北京", name = "张三", "上海", age = "23", "广州", depart = "销售部", "深圳"}

print(emp[5])

-- 声明一个元表
meta = {};

-- 将原始表和元表相关联
setmetatable(emp, meta)

-- 再定义一个普通表
other = {}

other[5] = "天津"
other[6] = "西安"

-- 指定元表为另一个普通表
meta.__index = other

-- 在原始表中若找不到,则会到元表指定的普通表中查找
print(emp[5])

输出
nil
天津

⭐️(3)__newindex 元方法

    当用户为 table 中一个 不存在索引key 赋值 时,就会自动调用元表的 _ _newindex 元方法。该重写的方法可以是一个函数,也可以是另一个

  • 如果重写的 _ _newindex 元方法是函数,且有返回值,则直接返回
  • 如果没有返回值,则返回 nil

例1:重写的方法是一个函数

emp  = {"北京", name = "张三", "上海", age = "23", "广州", depart = "销售部", "深圳"}

-- 声明一个元表
meta = {};

-- 将原始表和元表相关联
setmetatable(emp, meta)

-- 无返回值的情况 (有返回值的意义不大)
function meta.__newindex(tab, key, value)
	print("新增的key为:"..key..",value为:"..value)
	-- 将新增的 key-value 写入到原始表
	rawset(tab, key, value)
end

emp.x = "天津"

print(emp.x)

输出
新增的key为:x,value为:天津
天津

例2:重写的方法是一个

emp  = {"北京", name = "张三", "上海", age = "23", "广州", depart = "销售部", "深圳"}


-- 声明一个元表
meta = {};

-- 将原始表和元表相关联
setmetatable(emp, meta)

-- 再定义一个普通表
other = {}

-- 元表指定的另一个普通表的作用是,暂存新增加的数据
meta.__newindex = other

emp.x = "天津"

print(emp.x)
print(other.x)

输出
nil
天津

⭐️(4)运算符 元方法

    如果要为一个表扩展加号(+)、减号(-) 、等于(==) 、小于(<) 等运算功能,则可重写 相应的元方法

    例如,如果要为一个 table 扩展 加号(+) 运算功能,则可重写table 元表的 _ _add 元方法,而具体的运算规则,则是定义在该重写的元方法中的。这样,当一个 table 在进行加法运算时,就会自动调用其元表的 _ _add 元方法

emp  = {"北京", name = "张三", "上海", age = 23, "广州", depart = "销售部", 12, "深圳"}


-- 声明一个元表
meta = {
	__add = function(tab, num)
		-- 遍历tab中的所有元素
		for k, v in pairs(tab) do
			-- 若value为数值类型,则做算术加法
			if type(v) == "number" then
				tab[k] = v + num
			-- 若value为string,则做字符串拼接
			elseif type(v) == "string" then
				tab[k] = v..num
			end
		end
		-- 返回变化后的table
		return tab
	end
};

-- 将原始表和元表相关联
setmetatable(emp, meta)

empsum = emp + 5

for k, v in pairs(empsum) do
	print(k..":"..v)
end

输出
1:北京5
2:上海5
3:广州5
4:17
5:深圳5
depart:销售部5
name:张三5
age:28

    类似于加法操作的其它操作,Lua 中还包含很多:

在这里插入图片描述
在这里插入图片描述

⭐️(5)__tostring 元方法

    直接输出一个 table ,其输出的内容为类型与 table 的存放地址。如果想让其输出 table 中的内容,可重写 _ _tostring 元方法

emp  = {"北京", name = "张三", "上海", age = 23, "广州", depart = "销售部", 12, "深圳"}

-- 声明一个元表
meta = {
	__add = function(tab, num)
		-- 遍历tab中的所有元素
		for k, v in pairs(tab) do
			-- 若value为数值类型,则做算术加法
			if type(v) == "number" then
				tab[k] = v + num
			-- 若value为string,则做字符串拼接
			elseif type(v) == "string" then
				tab[k] = v..num
			end
		end
		-- 返回变化后的table
		return tab
	end, -- 注意!!! 有多个元方法时,这里必须要添加一个逗号

	__tostring = function(tab)
		str = ""
		-- 字符串拼接
		for k, v in pairs(tab) do
			str = str.." "..k..":"..v
		end

		return str
	end
};

-- 将原始表和元表相关联
setmetatable(emp, meta)

empsum = emp + 5

print(emp)
print(empsum)

输出
1:北京5 2:上海5 3:广州5 4:17 5:深圳5 depart:销售部5 name:张三5 age:28
1:北京5 2:上海5 3:广州5 4:17 5:深圳5 depart:销售部5 name:张三5 age:28

⭐️(6)__call 元方法

    当将一个 table函数形式 来使用时,系统会自动调用重写_ _call 元方法。该用法主要是可以简化对 table 的相关操作,将对 table 的操作 与 函数 直接相结合

emp  = {"北京", name = "张三", "上海", age = 23, "广州", depart = "销售部", 12, "深圳"}

-- 将原始表和匿名元表相关联
setmetatable(emp, {
	__call = function(tab, num, str)
		-- 遍历tab中的所有元素
		for k, v in pairs(tab) do
			-- 若value为数值类型,则做算术加法
			if type(v) == "number" then
				tab[k] = v + num
			-- 若value为string,则做字符串拼接
			elseif type(v) == "string" then
				tab[k] = v..str
			end
		end
		-- 返回变化后的table
		return tab
	end
})

newemp = emp(5, "-Lua")

for k, v in pairs(newemp) do
	print(k..":"..v)
end

输出
1:北京-Lua
2:上海-Lua
3:广州-Lua
4:17
5:深圳-Lua
depart:销售部-Lua
name:张三-Lua
age:28

如果 __call 用好了,可以大大减少我们的工作量!!!

⭐️(7)元表单独定义

    为了便于 管理复用,可以将元素单独定义为一个文件。该文件中 可定义 一个元表,且一般文件名与元表名称相同。例如新建一个 meta.lua文件,文件内容如下:

-- 声明一个元表
meta = {
	__add = function(tab, num)
		-- 遍历tab中的所有元素
		for k, v in pairs(tab) do
			-- 若value为数值类型,则做算术加法
			if type(v) == "number" then
				tab[k] = v + num
			-- 若value为string,则做字符串拼接
			elseif type(v) == "string" then
				tab[k] = v..num
			end
		end
		-- 返回变化后的table
		return tab
	end, -- 注意!!! 有多个元方法时,这里必须要添加一个逗号

	__tostring = function(tab)
		str = ""
		-- 字符串拼接
		for k, v in pairs(tab) do
			str = str.." "..k..":"..v
		end

		return str
	end
};

    若一个文件要使用其它文件中定义的元表,只需使用 require 元表文件名 即可将元表导入使用。

require "meta"

emp  = {"北京", name = "张三", "上海", age = 23, "广州", depart = "销售部", 12, "深圳"}

-- 将原始表和元表相关联
setmetatable(emp, meta)

empsum = emp + 5

print(emp)
print(empsum)

meta.__index = function(tab, key) --在自己文件中重下
	return "通过【"..key.."】访问的值不存在"
end

print(emp.x)

    如果用户想扩展该元表而又不想修改元表文件,则可在用户 自己文件中 重写其相应功能元方法 即可。

8.5.5 面向对象

    Lua 中没有类的概念,但通过 tablefunction元表 可以模拟和构造出具有 类这样功能的结构

⭐️(1)简单对象的创建

    Lua 中通过 tablefunction 可以创建出一个简单的 Lua 对象

  • tableLua 对象赋予 属性;
  • 通过 functionLua 对象赋予 行为,即 方法
-- 创建一个名称为animal的对象
animal = {name = "Tom", age = 5}

-- 添加方法,推荐使用方式1
-- 方式1:冒号中会自动包含一个self 参数,表示当前对象本身,相当于this
function animal:bark(voice)
	print(self.name.."在"..voice.."叫")
end

-- 方式2:该方式不能使用冒号:,来省略参数中的self
--~ animal.bark = function(self, voice)
--~ 	print(self.name.."在"..voice.."叫")
--~ end


-- =============== 使用对象 =======================
animal:bark("喵喵") -- 方式1,调用也是将 (点号) 换成 (冒号)
--~ animal.bark(animal, "喵喵") -- 方式2

-- 另定义一个对象,指向相同地址
animal2 = animal
-- 将 animal 置空
animal = nil
animal2.name = "Jerry"

animal2:bark("吱吱") -- 方式1
--~ animal2.bark(animal2, "吱吱") -- 方式2

⭐️(2)类的创建

    Lua 中使用 tablefunction元表 可以定义出

  • 使用一个 作为 基础类,使用一个 function 作为该基础类new() 方法。
  • 在该 new() 方法中 创建一个空表,再为该 空表 指定一个元表
  • 元表 重写 _ _index 元方法,且将基础表指定为重写_ _index 元方法。
  • 由于 new() 中的表是空表,所以用户访问的所有 key 都会从基础类)中查找
-- 创建一个类
Animal = {name = "no_name", age = 0} -- 基础类表

function Animal:bark(voice)
	print(self.name.."在"..voice.."叫")
end

-- 为该类添加一个无参构造器
function Animal:new()
	-- 创建一个空表
	local a = {} -- 局部的

	-- 为该空表a指定元表为当前基础类
	-- 在该空表a中找不到的key,会从self基础类表中查找
	setmetatable(a, {__index = self}) -- __index 只有在读的时候才会涉及

	-- 返回空表
	return a
end


--============== 创建对象=================

animal = Animal:new()
animal2 = Animal:new() -- 此时为两个对象

-- 下面这三个属性,是在表a中,和基础类表没任何关系
animal.name = "Tom"   -- 写操作
animal.age = 8
animal.type = "猫"
-- 直接从表a中读
print(animal.name.."今年"..animal.age.."岁了,它是一只"..animal.type)


function animal2:skill()  -- 写操作,也可以理解为键值对,key为方法名,value为函数
	return "小老鼠,会偷油"
end
-- 在自己的表a中查找没找到,则在基础类表中查找
print(animal2.name.."今年"..animal2.age.."岁了,它是一只"..animal2.skill())
8.5.6 协同线程与协同函数

⭐️(1)协同线程

    Lua 中有一种 特殊的线程,称为 coroutine 协同线程,简称 协程。其可以在运行时 暂停执行,然后转去执行其它线程,然后还可返回再继续执行没有执行完毕的内容。即可以“走走停停,停停再走走”。

    协同线程 也称为 协作多线程,在Lua 中表示 独立的执行线程任意时刻只会有一个协程执行,而不会出现多个协程同时执行的情况。

    协同线程的类型为 thread ,其启动暂停重启等,都需要通过函数来控制。下表是用于控制协同线程的基本方法。

-- 创建一个协同线程实例
crt = coroutine.create(
	function(a, b)
		print(a, b, a + b)
		-- 获取正在运行的协同线程实例,thread类型
		tr = coroutine.running();
		print(tr)
		-- 查看协同线程实例的类型
		print("crt-"..type(tr))
		-- 查看协同线程实例的状态
		print(coroutine.status(tr))

		-- 将当前协同线程实例挂起
		coroutine.yield() -
		-- coroutine.yield(a * b, a / b) 
		
		print("又重新返回到了协同线程")
	end
)

-- 启动协同线程实例
succ = coroutine.resume(crt, 3, 5) --返回启动成功与否的状态
print(succ) 

-- 查看crt的类型
print("main-"..type(crt))
-- 查看协同线程实例的状态
print("main-"..coroutine.status(crt))

-- 继续执行协同线程
coroutine.resume(crt)

-- 查看协同线程实例的状态
print("main-"..coroutine.status(crt))

输出
3 5 8
thread: 00BBDF00
crt-thread
running
main-thread
main-suspended
又重新返回到了协同线程
main-dead

在这里插入图片描述

⭐️(2)协同函数

    协同线程 可以 单独 创建执行,也可以通过 协同函数调用 启动执行。使用 coroutinewrap() 函数创建的就是协同函数,其类型为 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 * b
	end
)


-- 调用协调函数,启动协同线程
result1, result2 = cf(3, 5)
print(result1, result2)

print("cf的类型为:"..type(cf))

-- 重启挂起的协同线程
result1, result2 = cf() -- 这里的参数可以省略
print(result1, result2)

输出
3 5
tr的类型为:thread
4 6
cf的类型为:function
又重新返回到了协同线程
8 15

8.5.7 文件IO

Lua中提供了大量对文件进行 IO 操作的函数。这些函数分为两类:静态函数实例函数

  • 所谓静态函数是指 通过 io.xxx() 方式对文件进行操作的函数,
  • 实例函数则是通过 Lua中 面向对象方式操作函数

⭐️(1)常用静态函数

A、io.open()

格式io.open (filename [, mode])
解析】以 指定模式 打开指定文件,返回要打开文件的 句柄,就是一个对象(后面会讲 Lua 中的对象)。其中模式 mode三种,但同时还可配合两个符号使用:

  • r只读,默认模式
  • w只写,写入内容会覆盖文件原有内容
  • a只写,以追加方式写入内容
  • +增加符,在 r+w+a+ 均变为了 读写
  • b二进制表示符。如果要操作的文件为二进制文件,则需要变为 rbwbab

B、io.input()

格式io.input (file)
解析】指定要读取的文件。

C、io.outout()

格式io.output (file)
解析】指定要写入的文件。

D、io.read()

格式io.read([format])
解析】以指定格式读取 io.input() 中指定的输入文件。其中 format 格式有:

  • *l :从 当前位置下一个位置 开始读取 整个行默认格式
  • *n :读取 下一个数字,其将作为浮点数整数
  • *a :从当前位置的 下一个位置 开始读取 整个文件
  • number :这是一个数字,表示要 读取的字符的个数

E、io.write()

格式io.write(data)
解析】将指定的数据 data 写入到 io.output()指定的输出文件

例如

  1. 新建一个 info.properties 文件,并写入以下内容:
name=张三
age=23
depart=market

在这里插入图片描述

  1. 操作文件
-- 以只读方式打开一个文件
file = io.open("info.properties", "r")

-- 指定要读取的文件为 file
io.input(file)

-- 读取一行数据
line = io.read("*l")

--只要有数据,就一直读下去
while line ~= nil do
	print(line)
	line = io.read("*l")
end

-- 关闭文件
io.close()

输出
name=张三
age=23
depart=market

⭐️(2)常用实例函数

A、file:read()

    这里的 file 使用的是 io.open() 函数返回的 file ,其实际就是 Lua 中的一个对象。其用法与 io.read()相同

-- 以只读方式打开一个文件
file = io.open("info.properties", "r")

-- 读取一行数据
line = file:read("*l")

--只要有数据,就一直读下去
while line ~= nil do
	print(line)
	line = file:read("*l")
end

-- 关闭文件
file:close()

B、file:write()

    用法与 io.write() 的相同。

C、file:seek()

格式file:seek ([whence [, offset]])
解析】该函数用于 获取设置 文件读写指针的当前位置。位置从 1 开始计数,除文件最后一行外,每行都有行结束符,其会占两个字符位置。位置 0 表示文件第一个位置的前面位置。

    当 seek() 为无参时会 返回读写指针当前位置。参数 whence 的值有三种,表示将指针定位的不同位置。而 offset 则表示相对于 whence 指定位置的偏移量offset默认值0 。为 表示 向后偏移,为 表示 向前偏移

  • set :表示将指针定位文件开头处,即 0 位置处
  • cur :表示指针保持 当前位置不变默认值
  • end :表示将指针定位文件结尾处

例如

  1. 新建一个 nums.txt 文件,并写入以下内容:
12345
67
890

在这里插入图片描述
2. 操作文件

-- 以只读方式打开一个文件
file = io.open("nums.txt", "r")

pos = file:seek()
print("当前位置:"..pos)

-- 读取一行数据
line = file:read("*l")
print(line)

pos = file:seek()
print("读取过一行的位置:"..pos)

pos = file:seek("set", 2)
print("返回到文件起始位置的后面第二个位置:"..pos)

-- 读取一行数据
line = file:read("*l")
print(line)

-- 关闭文件
file:close()

输出
当前位置:0
12345
读取过一行的位置:7
返回到文件起始位置的后面第二个位置:2
345

注: 仅供学习参考,如有欢迎指正!

  • 31
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
在互联网上有很多可以下载的Lua脚本编写教程,我将为你提供一种方法来下载这些教程。首先,你可以在搜索引擎中输入“Lua脚本编写教程下载”,为了方便,你还可以添加一些关键词如“免费”、“详细”等。搜索结果中显示很多网站或资源,你可以选择其中一种下载方式。 一种常见的下载方式是在网站上直接下载。例如,你可以找到一些IT学习网站或技术分享网站,它们通常提供各种编程教程,包括Lua脚本编写。在网站上,你可以找到适合你的能力水平的教程,并点击下载按钮。这样,你就能获取到相应的教程文件,一般是PDF或文本格式。 另外一种下载方式是通过在线教育平台。有一些在线教育平台专门提供各种编程教程,包括Lua脚本编写。你可以在这些平台上注册账号,然后搜索关于Lua脚本编写的教程。在你找到合适的教程后,你可以选择下载到你的设备上,方便随时学习。 无论你选择哪种方式进行下载,建议你在下载前先了解一下这些资源的评价和质量,确保下载到的教程是详细并且适合你的学习需求。另外,如果你有编程的基础,还可以参考Lua官方网站提供的文档和教程,这些资源通常是免费提供的。 总之,通过以上方法你应该能够找到并下载到详细Lua脚本编写教程。希望这些教程能够帮助你更好地学习Lua脚本编程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

酷酷的懒虫

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值