八、Lua脚本详解—— 超详细操作演示!

八、Lua脚本详解 —— 超详细操作演示!

    • 八、Lua脚本详解
      • 8.1 Lua 简介
      • 8.2 Linux 系统的Lua
          • 8.2.1 Lua 下载
          • 8.2.2 Lua 安装
          • 8.2.3 Hello World
      • 8.3 Win 系统的Lua
      • 8.4 Lua 脚本基础
          • 8.4.1 注释
          • 8.4.2 数据类型
          • 8.4.3 标识符
          • 8.4.4 运算符
          • 8.4.5 函数
          • 8.4.6 流程控制语句
          • 8.4.7 循环控制语句
      • 8.5 Lua 语法进阶
          • 8.5.1 table
          • 8.5.2 迭代器
          • 8.5.3 模块
          • 8.5.4 元表和元方法
          • 8.5.5 面向对象
          • 8.5.6 协同线程与协同函数
          • 8.5.7 文件IO
    • 九、分布式锁
      • 9.1 分布式锁的工作原理
      • 9.2 问题引入
      • 9.3 setnx 实现方式
      • 9.4 为锁添加过期时间
      • 9.5 为锁添加标识
      • 9.6 添加 Lua 脚本
      • 9.7 Redisson 可重入锁
      • 9.8 Redisson 红锁
      • 9.9 分段锁
      • 9.10 Redisson 详解

数据库系列文章:

关系型数据库:

  • MySQL —— 基础语法大全
  • MySQL —— 进阶


非关系型数据库:

  • 一、Redis 的安装与配置
  • 二、Redis 基本命令(上)
  • 三、Redis 基本命令(下)
  • 四、Redis 持久化
  • 五、Redis 主从集群
  • 六、Redis 分布式系统
  • 七、Redis 缓存

八、Lua脚本详解

8.1 Lua 简介

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

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

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

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

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 ,其启动暂停重启等,都需要通过函数来控制。下表是用于控制协同线程的基本方法。

在这里插入图片描述

⭐️(2)协同函数

    协同线程 可以 单独 创建执行,也可以通过 协同函数调用 启动执行。使用 coroutinewrap() 函数创建的就是协同函数,其类型为 function

    由于协同函数的本质就是函数,所以协同函数的调用方式就是标准的 函数调用方式。只不过,协同函数的调用会启动其内置的协同线程

8.5.7 文件IO

⭐️(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()指定的输出文件

⭐️(2)常用实例函数

A、file:read()

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

B、file:write()

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

C、file:seek()

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

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

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

九、分布式锁

9.1 分布式锁的工作原理

9.2 问题引入

9.3 setnx 实现方式

9.4 为锁添加过期时间

9.5 为锁添加标识

9.6 添加 Lua 脚本

9.7 Redisson 可重入锁

9.8 Redisson 红锁

9.9 分段锁

9.10 Redisson 详解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/295074.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【C语言深度解剖——第二节(关键字2)】《C语言深度解剖》+蛋哥分析+个人理解

今日的星辰辉映太古的源起&#xff0c;过往的故事指引明日的生死&#xff01;&#xff08;王者荣耀李信&#xff09; 本文由睡觉待开机原创&#xff0c;未经允许不得转载。 本内容在csdn网站首发 欢迎各位点赞—评论—收藏 如果存在不足之处请评论留言&#xff0c;共同进步&…

万界星空科技MES系统中的生产管理

MES系统能够帮助企业实现生产计划管理、生产过程控制、产品质量管理、车间库存管理、项目看板管理等&#xff0c;提高企业制造执行能力。 万界星空MES系统特点&#xff1a; 1. 采用强大数据采集引擎、整合数据采集渠道&#xff08;RFID、条码设备、PLC、Sensor、IPC、PC等&…

Ribbon客户端负载均衡

简介 Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。 简单的说&#xff0c;Ribbon是Netflix发布的开源项目&#xff0c;主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如连接超时&#xff0c;重试等…

已知输入图像大小为n、卷积核大小为f、卷积步长s,填充大小为p,求解输出图像大小。

问题描述&#xff1a;已知输入图像大小为n、卷积核大小为f、卷积步长s&#xff0c;填充大小为p&#xff0c;求解输出图像大小。 问题解答&#xff1a; 输出图像的大小可以使用以下的计算公式确定&#xff1a; 为了举例说明&#xff0c;假设有以下参数&#xff1a; 输入图像大…

深入理解Java中资源加载的方法及Spring的ResourceLoader应用

在Java开发中&#xff0c;资源加载是一个基础而重要的操作。本文将深入探讨Java中两种常见的资源加载方式&#xff1a;ClassLoader的getResource方法和Class的getResource方法&#xff0c;并介绍Spring框架中的ResourceLoader的应用。 1. 资源加载的两种方式 1.1 ClassLoader…

低代码开发平台支持复杂的业务逻辑和API对接吗

当今数字化时代&#xff0c;企业和组织面临着日益复杂的业务需求。为了应对这些挑战&#xff0c;低代码开发平台应运而生。白码低代码开发平台是一种高效、灵活且可扩展的开发工具&#xff0c;能够支持复杂的业务逻辑和第三方API对接&#xff0c;为企业带来更快速、更灵活的解决…

【Linux Shell】6. echo 命令

文章目录 【 1. 显示普通字符串 】【 2. 显示转义字符 】【 3. 显示变量 】【 4. 显示换行 】【 5. 显示不换行 】【 6. 显示命令执行结果 】 Shell 的 echo 指令用于字符串的输出。命令格式&#xff1a; echo string【 1. 显示普通字符串 】 #!/bin/bashecho "It is a …

OpenAI 拟每年投入 100-500 万美元,以获取新闻使用许可

最近两位媒体公司高层透露&#xff0c;OpenAI正积极与新闻出版公司进行谈判&#xff0c;提出每年投入100万至500万美元的费用&#xff0c;以获取将新闻文章用于训练大型语言模型的授权。 OpenAI目前正与大约十几家媒体公司进行谈判&#xff0c;但有报道称&#xff0c;即使对于…

【大数据】Zookeeper 集群及其选举机制

Zookeeper 集群及其选举机制 1.安装 Zookeeper 集群2.如何选取 Leader 1.安装 Zookeeper 集群 我们之前说了&#xff0c;Zookeeper 集群是由一个领导者&#xff08;Leader&#xff09;和多个追随者&#xff08;Follower&#xff09;组成&#xff0c;但这个领导者是怎么选出来的…

【公有云】通过AWS Endpoint service Endpoint 实现跨VPC请求收敛

背景 公司两个AWS VPC有互通的需求&#xff0c;分别是PROD-VPC和PCI-VPC。PCI是Payment Card Industry的简称&#xff0c;通常PCI-VPC部署支付后端服务。关于PCI的相关资料参考AWS PCI DSS&#xff0c;Payment Card Industry 数据安全标准。 PROD服务请求PCI服务是跨VPC访问&…

Spring官方移除Java8

大家好我是苏麟 , 今天聊聊怎么继续使用Java8做项目 . 在做项目的时候突然发现Java8没了 , 我心想 : " 嗯? IDEA出毛病了?" ,经过我仔细检查原来是spring官方不支持Java8了 . IDEA 内置的 Spring Initializr 创建 Spring Boot 新项目时&#xff0c;没有 Java 8 的选…

2024年如何借用电商新零售破局?新型商业模式——乐享甄选竞拍模式

2024年如何借用电商新零售破局&#xff1f;新型商业模式——乐享甄选竞拍模式 背景&#xff1a;经历疫情三年的黑天鹅&#xff0c;消费者对未来收入预期和不自信等悲观情绪&#xff0c;从而使得“勒紧腰带&#xff0c;少消费&#xff0c;不消费”&#xff0c;以简单实用成为了新…

静态网页设计——贵州美食(HTML+CSS+JavaScript)

前言 声明&#xff1a;该文章只是做技术分享&#xff0c;若侵权请联系我删除。&#xff01;&#xff01; 感谢大佬的视频&#xff1a; https://www.bilibili.com/video/BV1vC4y1K7de/?vd_source5f425e0074a7f92921f53ab87712357b 使用技术&#xff1a;HTMLCSSJS&#xff08;…

企业选择通配符证书和多域名证书

随着互联网的发展&#xff0c;用户渐渐不再满足于只创建一个网站&#xff0c;因此&#xff0c;网站越来越多&#xff0c;管理多个网站的SSL证书的成本和时间也随之增加。为了节省管理多个站点的SSL证书的时间与成本&#xff0c;很多用户会选择通配符SSL证书与多域名SSL证书。这…

基于萤火虫算法优化的Elman神经网络数据预测 - 附代码

基于萤火虫算法优化的Elman神经网络数据预测 - 附代码 文章目录 基于萤火虫算法优化的Elman神经网络数据预测 - 附代码1.Elman 神经网络结构2.Elman 神经用络学习过程3.电力负荷预测概述3.1 模型建立 4.基于萤火虫优化的Elman网络5.测试结果6.参考文献7.Matlab代码 摘要&#x…

docker daemon.json配置bip项需要注意的地方

docker是现在部署服务比较常用的一种方式&#xff0c;但是docker0网络默认为172.17.x.x网段&#xff0c;172.17这个网段是私有化ip段&#xff0c;而且在企业内部使用比较常见的一个网段&#xff0c;docker0创建后会创建一个172.17网段的路由&#xff08;可以通过ip route查看&a…

Jenkins分布式实现: 构建弹性和可扩展的CI/CD环境!

Jenkins是一个流行的开源持续集成&#xff08;Continuous Integration&#xff0c;CI&#xff09;和持续交付&#xff08;Continuous Delivery&#xff0c;CD&#xff09;工具&#xff0c;它通过自动化构建、测试和部署过程&#xff0c;帮助开发团队更高效地交付软件。Jenkins的…

Socks5代理ip和Https代理ip的区别,该如何选择?

Socks5代理和HTTPS代理都是计算机网络中的代理服务器&#xff0c;它们可以用于在客户端和其他服务器之间建立连接并充当中间人。 两种代理类型都有其优缺点和适用场景。 一、什么是Socks5代理 Socks5代理Socks5代理是一个网络协议&#xff0c;通过该协议可以建立TCP和UDP连接…

智慧校园全空间三维电子沙盘系统

一、概述 易图讯科技&#xff08;www.3dgis.top&#xff09;采用大数据、云计算、虚拟现实、物联网、AI等先进技术&#xff0c;自主可控高性能WebGIS可视化引擎&#xff0c;支持多用户客户端通过网络请求访问服务器地图和专题数据&#xff0c;提供地理信息数据、专题数据的并发…

2023-2024学年上学期算法设计与分析题期末考试模拟卷

2023-2024学年上学期算法设计与分析题期末考试模拟卷 文章目录 2023-2024学年上学期算法设计与分析题期末考试模拟卷单选题程序填空题输入格式:输出格式:输入样例1:输出样例1: 主观题 注意&#xff1a;该题集非标准答案&#xff0c;仅供参考&#xff0c;如果异议&#xff0c;请…