数据库系列文章:
关系型数据库:
非关系型数据库:
????Lua 是一个由标准 C 语言 开发的、开源的、可扩展的、轻量级的、弱类型的、解释型脚本语言, 是 于 1993 年由 巴西里约热内卢天主教大学的三人研究小组使用标准 C 语言开发。
????Lua 的官网 为: https://www.lua.org/
Lua 是一门 脚本语言,和 Shell、Python 是同一种类型。
用的最多的是 Unity 手游,做 热更新 方案;Nginx 也有应用。
????若要使用 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 系统就好了。
??(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、运行
????直接使用文件名即可运行。
????这里要安装的是在 Windows 系统中 Lua 的运行环境。最常用的为 SciTE 。
????SciTE 是一款 Lua 脚本 测试编辑器,提供 Lua 的编辑运行环境。其官方下载地址为: https://github.com/rjpcomputing/luaforwindows/releases 。 下载完直接运行 exe
文件安装。
????SciTE 提供了两种运行方式:命令行窗口运行方式 与 Lua 脚本的编辑运行环境。
????除了SciTE ,还有像 LuaDist 、 LuaRocks 等。
????Lua 的 行注释 为两个连续的减号, 段注释 以--[[
开头,以 --]]
结尾。
????不过,在 调试过程 中如果想 临时取消
段注释,而直接将其标识删除,这样做其实并不好。因为有可能还需要再添加上。而段注释的写法相对较麻烦。
--[[
前再加一个减号,即可使段注释不起作用。其实就是使两个段注释标识变为了两个行注释。--~ 行注释,(快捷键为 Ctr + Q)
-- 行注释,(波浪号 ~ ,为快捷键自动生成的)
--[[段注释
print("Hello, Lua")
--]]
---[[取消段注释
print("Hello, Lua")
--]]
????Lua 中有 8 种 类型,分别为: nil
、 boolean
、 number
、 string
、 userdata
、 function
、 thread
和 table
。
type()
函数可以查看一个数据的类型,例如, type(nil)
的结果为 nil
, type(123)
的结果为 number
。-- string 演示
str1 = "中国"
str2 = '北京'
str3 = [[深圳
广州
上海]]
print(str1)
print(str2)
print(str3)
--[[输出:
中国
北京
深圳
广州
上海
--]]
????程序设计语言中的标识符主要包含 保留字、变量、常量、方法名、函数名、类名 等。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 = 北京
????运算符是一个特殊的符号,用于告诉解释器执行特定的 数学 或 逻辑运算。Lua 提供了以下几种运算符类型:
??(1)算术运算符
????下表列出了 Lua 语言中的常用算术运算符,设定 A
的值为 10
, B
的值为 20
:
注意,
//
需要在 Lua5.3 版本以上,所以当前 SciTE 中无法看到效果。print()
函数输出该变量。??(2)关系运算符
????下表列出了 Lua 语言中的常用关系运算符,设定 A
的值为 10
, B
的值为 20
:
??(3)逻辑运算符
????注意, Lua 系统将 false
与 nil
作为 假,将 true
与 非nil
作为 真,即使是 0
也是真。
????下表列出了Lua 语言中的常用 逻辑运算符,设定 A
的值为 true
, B
的值为 false
:
??(4)其他运算符
????下表列出了 Lua 语言中的 连接运算符 与 计算 表
或 字符串
长度 的运算符:
str = "abcdefg"
print(#str) -- 输出 7
????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
????Lua 提供了 if
作为 流程控制语句。
??(1)if 语句
????Lua 提供了 if...then
用于表示条件判断,其中 if
的判断条件可以是 任意表达式。 Lua 系统将 false
与 nil
作为假,将 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
嵌套语句。注意,不能使用 else
与 if
两个关键字的联用形式 ,即不能使用 else if
来嵌套 if
语句。
a = 5
if(a > 0) then
print("num > 0")
elseif a == 0 then
print("num = 0")
else
print("num < 0")
end
????Lua 提供了四种循环控制语句: while...do
循环、 repeat...until
循环、数值 for
循环,及 泛型 for
循环。同时, Lua 还提供了 break
与 goto
两种循环流程控制语句。
??(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 中 不支持 双冒号 的语句标记。
??(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
也可以定义出类似 map
的 key-value
数据结构。其可以定义 table
时直接指定 key-value
,也可单独指定 key-value
。而访问时,一般都是通过 table
的 key
直接访问,也可以数组索引方式来访问,此时的 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
个元素值。 i
与 j
是可选的,默认 i
为 1
, j
为数组的最后一个元素。 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)
是一个用于比较 a
与 b
的函数, a
与 b
分别代表数组中的两个相邻元素。
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
,则排序会 报错。
????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
--]]
????模块是 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()
函数是有返回值的,返回的就是模块文件最后 return
的 table
。可以使用一个变量接收该 table
值 作为模块的别名,就可以 使用 别名 来访问模块了。
-- 导入一个模块
rect = require "rectangle"
-- 访问模块的属性,调用模块的函数
print(rect.pi) -- 输出:3.14
print(rect.perimeter(3, 5)) -- 输出:16
print(rect.area(3, 5)) -- 输出:15
-- 访问模块中与模块无关的内容
print(goldenRatio) -- 输出:0.618
print(maxCircularArea(4, 5)) -- 输出:12.56
-- print(circularArea(2)) -- 报错,局部的不能访问
??(3)再看模块
????模块文件中一般定义的 变量 与 函数 都是模块 table
相关内容,但也可以定义其它与 table
无关的内容。这些 全局变量与函数 就是 普通的全局变量与函数,与模块无关,但会随着模块的导入而同时导入。所以在使用时可以直接使用,而无需也不能添加模块名称。
????元表,即 Lua 中普通 table
的 元数据表,而 元方法 则是元表中定义的普通表的默认行为。Lua 中的每个普通 table 都可为其定义一个元表,用于扩展该普通 table 的行为功能。例如,
??(1)重要函数
元表中有两个重要函数
??(2)__index 元方法
??(3)__newindex 元方法
??(4)运算符 元方法
??(5)__tostring 元方法
??(6)__call 元方法
??(7)元表单独定义
??(1)简单对象的创建
??(2)类的创建
??(1)协同线程
??(2)协同函数
??(1)常用静态函数
A、io.open()
【格式】io.open (filename [, mode])
【解析】以 指定模式 打开指定文件,返回要打开文件的 句柄,就是一个对象(后面会讲 Lua 中的对象)。其中模式 mode
有三种,但同时还可配合两个符号使用:
r
:只读,默认模式w
:只写,写入内容会覆盖文件原有内容a
:只写,以追加方式写入内容+
:增加符,在 r+
、 w+
、 a+
均变为了 读写b
:二进制表示符。如果要操作的文件为二进制文件,则需要变为 rb
、 wb
、 ab
。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 为正表示向后偏移,为负表示向前偏移。