目录

lua 脚本基本语法

前言

Lua /'luːə/ 是一个小巧且速度极快的脚本语言,由标准 C 编写而成,几乎在所有操作系统和平台上都可以编译运行,是作为嵌入式脚本的最佳选择。本人常用它来对文本内容进行批处理。

Lua 简介

1、lua 概述、官网、资料

Lua 是一个小巧的脚本语言,标准 C 编写而成,它可以被 C/C++ 代码调用,也可以反过来调用 C/C++ 的函数,几乎在所有操作系统和平台上都可以编译、运行。Lua 并没有提供强大的库,一个完整的 Lua 解释器不过 200KB,但它在所有脚本引擎中速度是最快的,是作为嵌入式脚本的最佳选择。Lua 是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个由 Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo 三人所组成的研究小组于 1993 年开发的。
安装 lua 后,其包括三部分:

  • lua(解释器):
    用于运行 lua 脚本文件。例如:lua test.lua命令是运行 test.lua 脚本文件。
  • luac(编译器):
    用于加密 lua 脚本文件(但可逆向解密,参考网文)。例如:lua -o t.lua test.lua命令对 test.lua 脚本文件加密并生成 t.lua 加密文件,使用同样方式运行脚本加密文件即可:lua t.lua
  • liblua.a(静态库):
    用于集成到其它程序中,例如编译一个 C 程序,调用 lua 操作 API 运行 lua 脚本。如果需要动态库,则需要修改 Makefile 文件并重新编译,具体请参考网文

资料:官网网文教程菜鸟教程Lua 5.3 API 参考手册Lua 5.3 参考手册
Lua API中国顶级程序员-网游大咖-云风(吴云洋)-博客 + 他的git仓库 + Lua 编程技巧

Lua 安装

1、lua 解释器下载与安装
1
2
3
4
5
6
wget http://www.lua.org/ftp/lua-5.4.3.tar.gz  #下载
tar zxf lua-5.4.3.tar.gz                      #解压
cd lua-5.4.3                                  #进入文件夹
make all test                                 #编译
sudo make install                             #安装(安装到 /usr/local/bin/:lua(解释器)、luac(编译器),及 /usr/local/lib/:liblua.a(静态库))
lua -v                                        #查看版本(用于验证是否安装成功)
2、lua 脚本的编写与运行

方法一:(1)编写脚本文件(但不指出 lua 脚本解析器),(2)在命令窗口运行脚本。

1
2
--(1)--
print("Hello World!")     --创建 HelloWorld.lua 脚本文件,在其增加此行脚本内容

1
2
3
##(2)##
chmod 777 HelloWorld.lua  #修改 HelloWorld.lua 文件权限,使其权限为可执行文件
lua ./HelloWorld.lua      #在命令窗口运行脚本文件,结果为打印出:Hello World!

方法二:(1)编写脚本文件(首行指出 lua 脚本解析器),(2)在命令窗口运行脚本。

1
2
3
#!/usr/local/bin/lua
---(1)---                 --上行表示指定 lua 脚本解析器执行。★★注意:上行必须使用 \n 来换行,不能是 \r\n 之类的来换行! 否则会提示 ^M 错误★★
print("Hello World!")     --创建 HelloWorld.lua 脚本文件,在其增加这里三行脚本内容

1
2
3
###(2)###
chmod 777 HelloWorld.lua  #修改 HelloWorld.lua 文件权限,使其权限为可执行文件
./HelloWorld.lua          #在命令窗口运行脚本文件,结果为打印出:Hello World!

说明三:lua 脚本解析器支持交互式编程

1
2
3
4
5
lua                       #在命令窗口运行lua脚本解析器进入交互式编程,
>                         #进入了交互式编程,这里可以即时编写一行脚本并按回车执行:
> print("Hello World!")  #编写一行脚本内容,按回车后,
Hello World!             #执行脚本(本例为打印信息)。
> 
3、lua 在 Windows 上安装

方法一:安装现成集成环境(不过 lua 的版本则比较旧)

  1. 安装 SciTE 的 IDE 集成环境:网址
  2. 安装 LuaDist 的集成库(已不维护):网址
  3. 安装 LuaRocks 的集成库:网址
  • 例如:安装 SciTE 的 IDE 集成环境后,直接在命令窗口运行 lua 脚本文件即可:
1
lua.exe .\HelloWorld.lua  #直接在命令窗口运行lua脚本文件

方法二:自己编译自己安装(可以使用 lua 的最新版本)

  1. 下载 TDM-GCC 编译工具,不需要勾选检测是否最新版本,直接点击Creat安装即可(图示),最后在命令窗口输入gcc -v可查看 gcc 版本。
  2. 下载 lua 源代码,解压文件并编译。【网上也有已编译好的 lua
1
2
3
4
5
gcc -v                  #查看gcc版本(用于验证TDM-GCC是否安装成功)
cd lua-5.4.4            #进入lua文件夹(刚才文件解压的根文件夹)
mingw32-make mingw      #编译lua源代码(在src\里生成:lua.exe(解释器)、luac.exe(编译器),liblua.a(静态库))
cd src                  #进入src\目录
lua.exe -v              #查看lua版本(用于验证lua是否编译成功)
  1. 将 lua 加入环境变量(图示),但建议直接把lua.exe、luac.exe、liblua.a、lua54.dll剪切到C:\Windows目录下。

备注:

Lua 语法

1、lua 概述

lua 由标准 C 编写而成,可以被 C/C++ 代码调用,也可以反过来调用 C/C++ 的函数,基于此我们可以很容易想像出它的语法一定会与 C 语言很相似。其中独立语句可以使用;分号分隔,也可以不用。还有特别说明一下,Lua 的所有变量其实都是字符,例如直接打印一个函数返回值return 100,虽然它是number数据类型,但打印结果还是字符串:100。

2、lua 关键字

一、lua 的关键字:

说明一、lua 关键字主要包括:声明、函数、跳转、返回、结束、判断、循环、真假、与或非等语句关键字。下面列出 22 个关键字:

关键字 关键字 关键字 关键字
local if then true
function elseif false
goto else nil
break for in or
return while do and
end repeat until not

二、Lua 的数据类型:

说明二、Lua 数据类型主要包括:空值、布尔值、数值、字符串、数据、线程、表这些数据类型,它们通过type()函数返回值获得(返回为字符串,由字符串单词指出数据类型)。下面列出 8 个数据类型:

关键字
含义
“nil” 空值(在条件表达式中相当于 false)
“boolean” 布尔值,包含两个值:true 和 false
“number” 表示双精度类型的实浮点数
“string” 字符串由一对双引号或单引号来表示
“function” 由 C 或 Lua 编写的函数
“userdata” 表示任意存储在变量中的 C 数据结构
“thread” 表示执行的独立线路,用于执行协同程序
“table” Lua 中的表其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过“构造表达式”来完成,最简单构造表达式是{},用来创建一个空表
补充说明 type()返回都是字符串!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
--类型打印实例--
tb = {}  aaa = nil
print(type(tb))                 --> table
print(type("Hello world"))      --> string
print(type(10.4*3))             --> number
print(type(print))              --> function
print(type(type))               --> function
print(type(true))               --> boolean
print(type(nil))                --> nil
print(type(type(print)))        --> string !!!
if type(123) == "number" then   --判断方法
end
if type(aaa) == "nil" then      --判断方法
end

三、Lua 的运算符:

说明三、Lua 运算符主要包括:算术、关系、逻辑、其它这几类运算符。它们有不同的优先级(强烈建议使用()指定),下面列出 8 级优先级:

优先级
运算符
备注
最高 ^ 连续几个^则是优先从右到左组合 x^y^z == x^(y^z)
not - - 为负号
* / %
+ -
..
< > <= >= ~= ==
and
最低 or

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
--算术运算符--
+             --加法
-             --减法
*             --乘法
/             --除法
%             --取余
^             --乘幂(10^2为100)
-             --负号(-10)

--关系运算符--
==            --等于
~=            --不等于
>             --大于
<             --小于
>=            --大于等于
<=            --小于等于

--逻辑运算符--  (用于条件判断中:if() elseif() while() until())
and           --逻辑与操作符(如:(A and B)) 
or            --逻辑或操作符(如:(A or  B)) 
not           --逻辑非操作符(如:not(A and B)) 

--其它运算符--
#             --返回字符串或数组的长度(如:#"Hello" 返回字符个数 5)(如果存在`nil`的组员,则计算组员个数可能出错!!!)
:             --表示表员/组员为函数且以隐式方式把表作为入口参数(如:file:read() 表示[表file]的表员是read()函数,并以隐式方式把[表file]作为入口参数,在函数内访问表员i写作 self.i )
.             --表示表员/组员 (如:tb.i 表示 表tb的表员i)
..            --连接两个字符串(如:a="Hello " b="World" print(a..b),结果为:Hello World)
...           --可变符:select("#",...)则返回可变参数的个数
              --        select(  3,...)则返回从第3开始的所有参数
              --        (...)表示函数入口的变长参数
              --        {...}表示由当前函数变长参数构成的数组

四、Lua 的注释:

说明四、Lua 注释主要包括:行、块两类注释。下面列出 2 种注释演示:

1
2
3
4
5
6
--这是一行注释

--[[
多行注释
多行注释
--]]
3、lua 变量

一、Lua 的变量:

说明一、Lua 变量有三种类型:全局变量、局部变量(local声明)、表中的域,它们的默认值(没有初始化时)均为nil。下面是展示变量的声明和初始化:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
tb = {}     --这是一个没赋值的全局数组/表
tb.i        --这是一个表的表员(《指定键名》为"i"的表员)
tb["i"]     --这是一个表的表员(《指定键名》为"i"的表员)
tb[i]       --这是一个数组组员(【没有键名】的第i个组员)
v = 1       --这是一个初始值为1的全局变量
a,b = a,v   --这是多个变量给多个变量赋值,右边的变量值是本行命令之前的缓冲值(固定不会变),不会受本行命令影响!
a,b = b,a   --同上,结果就是 a 与 b 数值交换
a,b = 1     --等同 a,b = 1,nil
a =         --等同 a = nil
b = nil     --赋空值,相当于删除全局变量(释放内存)
a,b = f()   --函数多个返回值依次赋值a,b,如果只有一个返回值则 b=nil
local c = 1 --这是一个局部变量,局部变量的访问速度比全局变量更快

二、Lua 的表/数组:

说明二、Lua 表/数组主要分为:一维数组、多维数组。表/数组的元素组成:键(名)=值。关于数组与表区别:数组其实就是一个有序的表。关于我对【表员】和〖组员〗的定义:有或无《键名》的元素都称为【表员】,没有《键名》的元素称为〖组员〗。不建议〖组员〗的数值设置为nil,否则会影响相关函数操作!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-----【一维数组】-----
array = {a="a","1",b="b",c}     --数组 array, 共用一个组员(组员编号默认从1计起,默认为一维数组)
print(array.c)                  --打印《指定键名》为 "c"的表员:nil(未赋值)
print(array.a)                  --打印《指定键名》为 "a"的表员:a
print(array["b"])               --打印《指定键名》为 "b"的表员:b
print(array[1])                 --打印【有序组员】的第一个组员:1
print(array[2])                 --打印不存在组员:nil(组员编号默认从1计起)
print(array[0])                 --打印不存在组员:nil(组员编号默认从1计起)
print(array["abc"])             --打印不存在表员:nil

array = {}                      --数组 array(空数组,需要初始组员编号及数值)
for i = -2,2,1 do
	array[i] = i*2              --初始组员编号及数值(本例组员设置为从-2计起)
end                             --!!!但不建议设置编号为负数!!!
for i = -2,2,1 do
	print(array[i])             --打印所有的组员:-4 -2 0 2 4(共5行组员数值)
end

-----【多维数组】-----
array = {}                      --数组 array(空数组,需要初始组员编号及数值)
for i=1,3,1 do
	array[i] = {}               --数组 array 首先初始一维数组,(必须初始第一维后,再初始此一维中的第二维,否则视为非法语句!)
	for j=1,3,1 do
		array[i][j] = i*j       --数组 array 接着初始二维数组。(必须初始第一维后,再初始此一维中的第二维,否则视为非法语句!)
	end
end
for i=1,3,1 do
	for j=1,3,1 do
		print(array[i][j])      --打印所有的组员:1 2 3 2 4 6 3 6 9(共9行组员数值)
	end
end

三、lua 的字符串:

说明三、字符串语法包括"" '' [[]],其中[[]]中书写的特殊字符不需要转义。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
-----【字符串】-------
s = [[a\\\"\'[[b]c]]            --多行字符串:其语法不支持转义符,不支持连续多个“]”字符,但支持单个或隔开的“]”字符。
string1 = "Lua"                 --单行字符串:""  (注:"" 与 '' 没本质区别)
string2 = '\"runoob.com\"'      --单行字符串:''  (注:"" 与 '' 没本质区别)
string3 =                       --多行字符串:[[]](注:[[ 后可直接换行,不算入多行字符串的回车或换行!)
[[
"Lua
Script"
]]

print(s)                        --打印的信息为:a\\\"\'[[b]c(本例为了说明[[]]内特殊字符不需要书写转义符)
print("string1"    .. string1)  --打印的信息为:string1Lua  (使用 .. 拼接功能)
print("\"string1:\"", string1)  --打印的信息为:"string1:"  Lua
print("string2:"    , string2)  --打印的信息为:string2:    runoob.com
print("string3:"    , string3)  --打印的信息为:string3:    "Lua
                                --       Script"
                                --       (空行)

说明四、对于使用[[]]书写的字符串、或者通过文件方式读取的内容,lua 已自动为我们加入了转义符,所以无需关心特殊字符是否要转义。

特殊字符
字符
\\ 代表一个反斜线字符:\ (可见符)
\' 代表一个单引号字符:' (可见符)
\" 代表一个双引号字符:" (可见符)
\a 响铃
\b 退格
\f 换页
\n 换行
\r 回车
\t 水平制表(HT)(跳到下一个TAB位置)
\v 垂直制表(VT)
\0 空字符
\ddd 3 位八进制数所代表的任意字符(如:\777
\xhh 2 位十六进制所代表的任意字符(如:\xF5
4、lua 控制

一、循环控制语句:

说明一、循环控制语句包括:for do … endfor in do … endwhile() do … endrepeat … until()四种语法。循环支持嵌套,可以使用beakgoto(语法:goto label2::label2::)语句退出循环体。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
-----【数值for循环】-----
for i = 5,10,1 do               --循环条件为(i=5; i<=10; i+=1)(注1:不能递减![i]在循环体内只能读!递增值必须为正数!)
	print("i = ", i)            --循环体 do …… end 之间。     (注2:起始值、结束值、递增值为函数时,函数都只调用一次)
end                             --第一次循环打印:i =   5      (注3:for条件的变量不会因for循环体内的改动而改变!!!)
                                --第二次……
m = 10
for i=1,m,1 do                  --共循环10次 (注意:[m]在循环体内变化,不影响 for 条件判断!!!)
	print(i)                    --打印:1、2、3、…、10
	m = 2                       --注意:[m]在循环体内变化,不影响 for 条件判断!!!
end

-----【泛型for循环】-----
days = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"}
for k,v in ipairs(days) do      --使用〖组员〗迭代器(中途遇到组员值为`nil`立即结束)(注1:迭代器 iterator /ɪtə'reɪtə/)
	print(k, v)                 --第一次循环打印:1  Sunday                     (注2:泛型for迭代器函数要求返回格式为:数组键号(k)、组员数值(v))
end                             --第二次循环打印:2  Monday
                                --第三次……
s = "hello world from Lua"
for v in string.gmatch(s, "%a+") do
	print(v)                    --第一次循环打印:hello
end                             --第二次循环打印:world
                                --第三次……

-----【先判断再循环】-----
n = 10
while (n < 15) do               --循环条件为(n < 15)
	print("n = ", n)            --循环体 do …… end 之间。
	n = n + 1                   --[n]在循环体内变化,影响循环条件!
end

-----【先循环再判断】-----
n = 10
repeat                          --循环体 repeat …… until 之间。
	print("n = ", n)
	n = n + 1                   --[n]在循环体内变化,影响循环条件!
until (n > 15)                  --结束条件为(n > 15)(注意:这是结束条件!)

二、流程控制语句:

说明二、流程控制语句包括:if then … elseif then … else … end语法。流程支持嵌套,可以使用goto(语法:goto label2::label2::)语句退出执行体。其中 Lua 认为falseniltrue非 nil(也就是说数值 0 也为真!)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-----【一个执行体】-----
a = 10
if (a < 20) then                --判断条件为(a < 20)
	print("a < 20, a = " .. a)  --执行体 then …… end 之间。
end                             --打印信息:a < 20, a = 10

-----【两个执行体】-----
a = 20
if (a < 20) then                --判断条件为(a < 20)
	print("a <  20, a = " .. a) --执行体 then … else … end 之间。
else
	print("a >= 20, a = " .. a) --打印信息:a >= 20, a = 20
end

-----【多个执行体】-----
a = 100
if     (a == 10) then           --判断条件为(a == 10)
	print("a == 10")
elseif (a == 20) then           --判断条件为(a == 20)
	print("a == 20")
elseif (a == 30) then           --判断条件为(a == 30)
	print("a == 30")
else
	print("a == ??")
end
5、lua 函数

函数语法function name() … end。函数分为全局函数、局部函数(函数前加local关键字即可)。函数入口可以是一个或多个参数,也可以是可变参数;出口可以返回一个参数或多个参数,也可以是可变参数(如:return areturn a,breturn "abcd"return ...等)。


迭代器(iterator /ɪtə’reɪtə/):是一种可以遍历集合中所有元素的机制,其通过循环语句调用 迭代函数,逐一取出(遍历)符合条件的相关数据。技术特点:迭代函数在第一次被调用时初始化现场,之后调用后返回时会保持现场为下次接续调用准备(有点类似自动步枪,第一发手工上膛(初始化),之后射一发后会自动为下一发上膛)。关于迭代函数参考网文:lua 迭代器学习Lua 迭代器与泛型 for


闭包(closure /‘kləʊʒər/):将一个函数写在另一个函数之内,那么这个位于内部的函数便可以访问外部函数中的局部变量,这项特性称之为词法域。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-----【指定名称函数】-----
function num_max(num1, num2)             --指定名称函数为 num_max()!!!!!!!!!!!!
	if (num1 > num2) then
		result = num1
	else
		result = num2
	end
	return result                        --注:可以返回多个值,每个值以逗号隔开
end
print("10, 4, max = " .. num_max(10, 4)) --调用 print() 入口处调用函数 num_max()
print("5, 16, max = ",   num_max(5, 16)) --调用 print() 入口处调用函数 num_max()

-----【别名索引函数】-----
p_max = function(num1, num2)             --函数传递给变量 p_max !!!!!!!!!!!!!!!!
	if (num1 > num2) then
		result = num1
	else
		result = num2
	end
	return result                        --注:可以返回多个值,每个值以逗号隔开
end
print("10, 4, max = " .. p_max(10, 4))   --调用 print() 入口处调用函数 p_max()
print("5, 16, max = ",   p_max(5, 16))   --调用 print() 入口处调用函数 p_max()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-----【入口为可变参数】-----
function add(...)                        --可变参数函数 add(...)
	local s = 0  
	for k, v in ipairs{...} do           --{...}表示一个由所有变长参数构成的数组
		s = s + v
	end
	print("sum = " .. select("#",...))   -- 打印可变参数的个数
	print(            select(3,...))     -- 打印第3个到最后一个参数的数值(注:.. 只支持一个数值拼接)
	return s
end  
print(add(3,4,5,6,7))                    --有 5 个参数时的调用
print(add(3,4,5,6,7,8))                  --有 6 个参数时的调用

-----【入口参数为数组】-----
function add(a)                          --函数为 add(a)
	local s = 0  
	for k, v in ipairs(a) do             --ipairs(a)用来迭代数组(中途遇到组员值为`nil`立即结束)
		s = s + v
	end
	return s
end  
print(add({3,4,5,6,7}))                  --参数为数组{3,4,5,6,7}
print(add({3,4,5,6,7,8}))                --参数为数组{3,4,5,6,7,8}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
-----【泛型for迭代函数】-----
function iter(a, k)                      --迭代器函数[原型](★无状态迭代器★)---------------------------
	k = k + 1                            --★迭代函数的入口要求:1.状态常量(a)、2.控制变量(k)
	if a[k] then
		return k, a[k]                   --★返回数组的典型参数:1.控制变量(k)、2.返回值(v)
	end
end

function myipairs(a)                     --迭代器函数[封装](★无状态迭代器★)---------------------------
	return iter,a,0                      --★迭代器调用格式封装:1.迭代函数(f)、2.状态常量(a)、3.控制变量(k:初始值0)
end                                      --组织:f1(a) -> f2(a,k) -> k,v

array = {"aa", "bb", "cc", "dd"}
for k,v in myipairs(array) do            --与 ipairs() 一样用途的 myipairs() (泛型for缓存三个参数:迭代函数(f)、状态常量(a:数组)、控制变量(k)。[v]是每次取出的组员值)
   print(k,v)                            --第一次循环打印:1  aa
end                                      --第二次……

for k,v in iter,array,0 do               --★迭代器[原型]调用格式:1.迭代函数(f)、2.状态常量(a)、3.控制变量(k:初始值0)
   print(k,v)                            --第一次循环打印:1  aa
end                                      --第二次……

-----【闭包型迭代函数】-----
function iter(a)                         --★函数输入参数:个数/数值自由决定!!!
	local k = 0
	local c = #a                         --算出组员总数(注意:不是表员总数)
   
	return function()                    --★闭包函数(★多状态迭代器★)---------------------------------
		k = k + 1
		if k <= c then
			return c, k, a[k]            --★函数返回参数:个数/数值自由决定!
		end
	end
end

array = {"aa", "bb", "cc", "dd"}
for c,k,v in iter(array) do              --★for调用闭包方法!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	print(c,k,v)                         --第一次循环打印:4  1  aa
end                                      --第二次循环打印:4  2  bb
                                         --第三次……
p=iter(array) --★★闭包函数直接返回是一个函数变量,之后函数变量调用返回才是数据(留意其里面有两个return!)
while true do                            --★while调用闭包方法!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	local c,k,v=p()                      --闭包型迭代函数调用!!!!!!!!!!!!!!!(相当于C语言指针方式调用!不能直接调用!)
	if c ~= nil then
		print(c,k,v)                     --第一次循环打印:4  1  aa
	else                                 --第二次循环打印:4  2  bb
		break                            --第三次……
	end
end

p=iter(array);       local c,k           --★普通方式调用,实现典型计数器!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
c,k=p(); print("counter = "..k)          --第一次计数,打印结果:counter = 1
c,k=p(); print("counter = "..k)          --第二次计数,打印结果:counter = 2
c,k=p(); print("counter = "..k)          --第三次计数,打印结果:counter = 3
										 --  …                           …
---------------------------------------------------------------------
--执行效率高到低:无状态迭代器 → 闭包(多状态迭代器) → table(不推荐)
---------------------------------------------------------------------
6、lua 模块

主要包括三方面:
1、lua 调用 lua(lua 模块)
2、lua 调用 C(lua 包)
3、C 调用 lua(lua 函数)。
关于 lua 与 c 之前传递参数,lua 使用模拟栈方式实现参数传递,其中-1表示栈顶,1表示栈底,具体可参考网文:Lua调用C/C++ → 4.1.堆栈结构解析

一、【lua 模块】之 lua 调用 lua 模块

  • 【lua 模块】其实就是单独写的一个lua 脚本文件,它实质就是表,封装到一个 lua 脚本文件的全局表(表名与文件同名),其表员必须是有键名的表员。
  • lua 模块路径搜索策略:函数require()→ 全局变量package.path→ 环境变量LUA_PATHpackage.loadfile → 加载 Lua 脚本文件
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
----------------------
----【my.lua】脚本文件
----------------------
my = {}                                     --全局数组:my

my.static = "abc"                           --全局变量:my.static

local function func1()                      --局部函数:func1()
	return "func1()"
end

function my.func2()                         --全局函数:my.func2()
	print("run my.func2()")
end

function my.func3()                         --全局函数:my.func3()
	print("run my.func3() -> "..func1())
end
 
return my

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
------------------------
----【test.lua】脚本文件
------------------------
--#!/usr/local/bin/lua
--print(package.path)                         --打印出lua模块加载路径。如果【my.lua】模块无法加载,则需要增加指定加载路径,例如:┐
--package.path = "./123/?.lua;"..package.path --在加载模块前[临时]加入指定加载路径(即:在 require("my") 前)←─────────┘
--print(package.path)                         --打印出lua模块加载路径。(从打印信息可以看到增加了"./123/?.lua"路径)

require("my")                                 --加载模块(也可这样写:require "my")(/rɪ'kwaɪər/)
local me = require("my")                      --为模块起别名
print("get my.static: "..my.static)           --读取模块变量(全局):my.static,打印信息:get my.static: abc
my.func2()                                    --调用模块函数(全局):my.func2 ,打印信息:run my.func2()
my.func3()                                    --调用模块函数(全局):my.func3 ,打印信息:run my.func3() -> func1()
me.func3()                                    --调用模块函数(全局):my.func3 ,打印信息:run my.func3() -> func1()

二、【lua 包】之 lua 调用 c 语言编译库

  • 【lua 包】其实就是使用 C/C++ 语言按 lua 标准专为 lua 编写的扩展模块,在 linux 下编译为 so 动态库文件(windows 为 dll),其极大的提高 Lua 的可扩展性和可用性。
  • lua 包的路径搜索策略:函数require()→ 全局变量package.cpath→ 环境变量LUA_CPATHpackage.loadlib → 加载 so 或 dll 库文件
  • 关于 lua 包参考网文:c 语言的 lua 库编写
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//----------------------
//--【myc.c】C文件源代码
//----------------------
#include <stdio.h>
#include <string.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h" //lua作者写错? 为什么不叫<luaxlib.h>

static int add(lua_State *L)                  //案例:算出两个数的和-----------
{
	double op1 = luaL_checknumber(L, 1);      //lua入口参数<1>
	double op2 = luaL_checknumber(L, 2);      //lua入口参数<2>

	double op3 = op1 + op2;                   //本函数运算

	lua_pushnumber(L, op3);                   //lua出口参数<1>
	return 1;                                 //lua出口参数<个数>
}
                                              //向lua注册可用函数--------------
static luaL_Reg myfunc[] = {                  //函数注册表:
    {"add", add},                             //add()
    {NULL, NULL}                              //结束符!!!!
};
int luaopen_myc(lua_State *L)                 //lua要求注册函数必须为: luaopen_xxx, xxx 就是模块名称(包括编译出来so库文件的名称也必须为xxx)
{	                                                           //本例: luaopen_myc, 编译生成库文件为[myc.so]
    luaL_newlib(L, myfunc);                   //函数注册(告诉lua知道有哪些可使用函数)
    return 1;
}

/*-------------获取其它类型入口参数函数-------------
//"lauxlib.h"
luaL_checkany     --检测任何值(可以为 nil)
luaL_checknumber  --检测一个值是否为 number (double),并返回。
luaL_checkint     --检测一个值是否为 number (double),并转换成(int)
luaL_checklong    --检测一个值是否为 number (double),并转换成(long)
luaL_checkinteger --检测一个值是否为 number (double),并转换成 lua_Integer(prtdiff_t),在我的机子上 prtdiff_t 被定义为(int)
luaL_checkstring  --检测一个值是否为 string,并返回。
luaL_checklstring --检测一个值是否为 string,并将字符串长度传递给[入口参数(指针)]中返回
luaL_checkudata   --检测自定义类型
*/
/*-------------常用参数压栈函数(C->lua)-----------------
//"lua.h"
void        (lua_pushnil)     (lua_State *L);
void        (lua_pushnumber)  (lua_State *L, lua_Number  n);
void        (lua_pushinteger) (lua_State *L, lua_Integer n);
const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len);
const char *(lua_pushstring)  (lua_State *L, const char *s);
*/

1
2
3
4
#---------------------------
#---【myc.so】为编译生成so库
#---------------------------
gcc -shared -fPIC myc.c -o myc.so             #直接在命令窗口使用gcc编译生成so库文件,为专给lua调用的模块(lua包)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
------------------------
----【test.lua】脚本文件
------------------------
--#!/usr/local/bin/lua
--print(package.cpath)                        --打印出lua模块加载路径,如果【myc.so】模块无法加载,则需要增加指定加载路径,例如:┐
--package.cpath = "./aa/?.so;"..package.cpath --加载第三方的so库文件(windows系统则是dll库文件) ←───────────────┘
--print(package.path)                         --打印出lua模块加载路径(从打印信息可以看到增加了"./aa/?.lua"路径)

myc = require("myc")                          --加载模块(也可这样写:require "myc")(/rɪ'kwaɪər/)
print(myc.add(1.0, 2.0))                      --打印出来的结果:3.0

三、【lua 函数】之 c 调用 lua 函数

  • 事前准备:由于 lua 源代码默认只编译生成【静态库】,但 c 程序需要调用其【动态库】才可编译运行!所以我们首先需要重新编译及安装 lua 软件。
  • 关于 lua 堆栈操作:只有 c 调用 lua 需要手工 pop 栈,而 lua 调用 c 不需要。
  • 关于 lua 堆栈网文:Lua 调用原理展示(lua 的堆栈)网文2网文3
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#-------------------------------------------------------
#【1】修改 ~/lua-5.4.3/Makefile(用文本编辑工具修改)
#-------------------------------------------------------
# 将>>>>>>>>>>>>
TO_LIB= liblua.a
# 修改为
TO_LIB= liblua.a liblua.so  ###表示同时编译静态库和动态库

#-------------------------------------------------------
#【2】修改 ~/lua-5.4.3/src/Makefile(用文本编辑工具修改)
#-------------------------------------------------------
# 将>>>>>>>>>>>>
CFLAGS= -O2 -Wall -Wextra -DLUA_COMPAT_5_3 $(SYSCFLAGS) $(MYCFLAGS)
# 修改为
CFLAGS= -O2 -Wall -Wextra -DLUA_COMPAT_5_3 $(SYSCFLAGS) $(MYCFLAGS) -fPIC ###增加编译动态库选项

# 在>>>>>>>>>>>>
LUA_A=  liblua.a
# 下行增加 
LUA_SO= liblua.so ###表示新建一个目标,要编译出lua动态库,编译出来的动态库文件名字为:liblua.so

# 将>>>>>>>>>>>>
ALL_T= $(LUA_A) $(LUA_T) $(LUAC_T)
# 修改为
ALL_T= $(LUA_A) $(LUA_T) $(LUAC_T) $(LUA_SO) ###同时生成动态库

# 在>>>>>>>>>>>>
$(LUA_A): $(BASE_O)
	$(AR) $@ $(BASE_O)
	$(RANLIB) $@
# 下行增加 (###同时生成动态库,注意第二行前头须是Tab空格)
$(LUA_SO): $(CORE_O) $(LIB_O)
	$(CC) -o $@ -shared $? -ldl -lm

########################################################
#【3】重新编译lua生成动态库(在命令窗口进入~/lua-5.4.3)
########################################################
make clean              #清除(清除之前编译的中间文件,否则可能编译出错!)
make all test           #编译
sudo make install       #安装(安装到 /usr/local/bin/:lua(解释器)、luac(编译器),及 /usr/local/lib/:liblua.a(静态库)、liblua.so(动态库))
sudo gedit /etc/profile #修改环境变量:使用文件编辑工具打开 /etc/profile(或~/.bashrc),在其最后添加一行(增加环境变量):
                        #              export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib/liblua.so
source /etc/profile     #生效环境变量。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
------------------------
----【call.lua】脚本文件
------------------------
function add(x, y)
	local z
	z = x + y                        --计算两个数的和
	return z, "lua"                  --返回“和”,及“lua”标识符(返回两个参数)
end

function dec(x, y)
	local z
	z = x - y                        --计算两个数的差
	return z, "lua"                  --返回“差”,及“lua”标识符(返回两个参数)
end

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//----------------------
//--【run.c】C文件源代码
//----------------------
#include <stdio.h>
#include <string.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h" //lua作者写错? 为什么不叫<luaxlib.h>

int main(int argc, char *argv[])
{
	double z = 0; 
	double x = 1.0; 
	double y = 2.1;
	lua_State *L;                    //声明Lua对象指针
	//int f0;

	//首先:打开lua环境-----------------------------------------------------------------------------------
	L = luaL_newstate();             //1.初始lua对象指针
	luaL_openlibs(L);                //2.打开Lua的高级库 [io,string,math,table]等

	//--------------------------------
	//加载lua脚本文件,执行lua中的函数
	//--------------------------------
	luaL_dofile(L, "call.lua");      //0.加载lua脚本文件(返回0表示正确)(紧接其后可加 lua_settop(L, 0) 确保栈底是空的)
	lua_getglobal(L, "add");         //1.通过名字获取lua函数>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

	lua_pushnumber(L, x);            //2.压入第一个入口参数(Lua与C/C++之间都是通过栈来通信的)
	lua_pushnumber(L, y);            //  压入第二个入口参数(Lua与C/C++之间都是通过栈来通信的)

	if ((lua_pcall(L, 2, 2, 0) == 0) //3.调用lua函数,告知有2个参数,2个返回值,最后0表示不提供错误处理函数(注:lua_call()入口没有错误函数,其它完全一样)
	&&  (lua_isnumber(L, 1) != 0))   //4.判断返回值是否合法(是否为 number -> double)(-1表示栈顶元素,1表示栈底,…)
	{
		z = lua_tonumber(L, 1);      //5.获取第一个参数返回值(-1表示栈顶元素,1表示栈底,…)(lua_pcall()已将入口参数删除,所以栈底就是第一个返回参数!)
		printf("add() return: %f,%s\n", z, lua_tostring(L, 2));
	}
	else
	{
		printf("[error] running function: %s\n", "add"); /*
		printf("[error] function return: %s\n", lua_tostring(L, 1)); */
	}

	/*调用lua脚本的 dec() 函数
	f0 = lua_gettop(L);              //x0.获取当前堆栈元素数量
	lua_getglobal(L, "dec");         //x1.通过名字获取lua函数>>>>>>>>>>>>>>>>>

	lua_pushnumber(L, x);            //x2.压入第一个入口参数(Lua与C/C++之间都是通过栈来通信的)
	lua_pushnumber(L, y);            //   压入第二个入口参数(Lua与C/C++之间都是通过栈来通信的)

	if ((lua_pcall(L, 2, 2, 0) == 0) //x3.调用lua函数,告知有2个参数,2个返回值,最后0表示不提供错误处理函数(注:lua_call()入口没有错误函数,其它完全一样)
	&&  (lua_isnumber(L, f0+1) != 0))//x4.判断返回值是否合法(是否为 number -> double)(-1表示栈顶元素,1表示栈底,…)
	{
		z = lua_tonumber(L, f0+1);   //x5.获取第一个参数返回值(-1表示栈顶元素,1表示栈底,…)(lua_pcall()已将入口参数删除,所以栈底就是第一个返回参数!)
		printf("dec() return: %f,%s\n", z, lua_tostring(L, f0+2));
	}
	else
	{
		printf("[error] running function: %s\n", "dec");
	}
	if ((f0 = lua_gettop(L) - f0) > 0)
	{
		lua_pop(L, f0);              //x6.删除[dec]函数堆栈所有元素<<<<<<<<<<<
	}*/

	lua_pop(L, -1);                  //6.删除Lua当前堆栈所有元素<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

	//最后:关闭lua环境-----------------------------------------------------------------------------------
	lua_close(L);

	L = NULL;
	return 0;
}
/*-------------常用参数获取并转换函数(C->lua)-----------------
//"lua.h"
lua_tonumber   --获取返回值,将转换为 number (double)
lua_tointeger  --
lua_tostring   --获取返回值,将转换为 string  (返回的是指针)

函数原型:
lua_Number   (lua_tonumberx)  (lua_State *L, int idx, int *isnum);
lua_Integer  (lua_tointegerx) (lua_State *L, int idx, int *isnum);
const char  *(lua_tolstring)  (lua_State *L, int idx, size_t *len);
int          (lua_toboolean)  (lua_State *L, int idx);
*/
/*-------------其他一些栈操作函数(C->lua)---------------------
int   lua_gettop    (lua_State *L);          //返回栈顶索引(即栈长度)  
void  lua_settop    (lua_State *L, int idx); //                
void  lua_pushvalue (lua_State *L, int idx); //将idx索引上的值的副本压入栈顶  
void  lua_remove    (lua_State *L, int idx); //移除idx索引上的值  
void  lua_insert    (lua_State *L, int idx); //弹出栈顶元素,并插入索引idx位置  
void  lua_replace   (lua_State *L, int idx); //弹出栈顶元素,并替换索引idx位置的值
*/

1
2
3
4
5
#----------------------
#-- 在命令窗口编译c程序
#----------------------
gcc run.c -o run -llua               #编译c程序,注意要加链接lua动态库:-llua
./run                                #运行c程序。最后结果打印:add() return: 3.100000,lua
7、lua 元表

Lua 提供了元表(MetaTable /‘metə/‘teɪbl/),允许我们改变 table 的行为,每个行为关联了对应的元方法。例如表:tb = {},当tb[1] = "abc"时,Lua 默认的行为就是新建一个键值,但如果使用元表的元方法可以改变这句表达式的行为,例如自动在值前面增加前缀"my-"字符(具体如何改变行为,学习后面知识点),则打印键值print(tb[1])信息为"my-abc"而非"abc",这就是元方法改变 table 的行为。还有 lua 没定义表的一些操作,需要我们来实现。例如:tab1 + tab2表示两个表相加,这句表达式意思是拼接两个表还是两个表所有键值相加?这就需要我们通过元方法来定义这句表达式实际行为。
关于元表参考网文:lua 元表详解

两个重要的函数 作用
setmetatable(table, metatable) 对指定 table 设置元表(metatable)
getmetatable(table) 返回对象的元表(metatable)

元方法
表达式
表现
__index = 基表没有的键的【元方法读】索引到【读元表】,其它情形的【元方法读写】表现为【读写基表】
__newindex = 基表没有的键的【元方法写(增)】索引到【写(增)元表】,其它情形的【元方法读写】表现为【读写基表】
__index
__newindex
= 当这两个元方法定义为同一个元表,表现为同时读写(增)基表和元表
__call 当函数调用(如:tab(a,b,c)中的tab原来是表,转化后则是个函数)
__tostring 转化为字符串(如:print(tab)中的tab原来是表,转化后则是字符串)
__add + 自定义两个表相加tab + tabx的行为
__sub - 自定义两个表相减tab - tabx的行为
__mul * 自定义两个表相乘tab * tabx的行为
__div / 自定义两个表相除tab / tabx的行为
__mod % 自定义两个表相余tab % tabx的行为
__unm - 自定义表取反-tab的行为
__concat .. 自定义两个表连接tab .. tabx的行为
__eq == 自定义两个表比较相等tab == tabx的行为
__lt < 自定义两个表比较小于tab < tabx的行为
__le <= 自定义两个表比较小于相等tab <= tabx的行为

一、默认元方法设置元表(默认元方法是无法查询及修改元表的)

1
2
3
4
5
6
7
tab  = {}                     --基表
mtab = {1,2}                  --元表
tb = setmetatable(tab, mtab)  --典型格式:把 mtab 设为 tab(及tb) 的元表
--tb = setmetatable({},{1,2}) --精简格式:为 tb 设置元表,与上面一样的,因为上面的 tab、mtab 代表两个数组 {}、{1,2}
print(tb[1], mtab[1])         --打印结果:nil  1 (默认元方法是无法查询及修改元表的!!!!!!)
tb[1] = 444  mtab[1] = 5
print(tb[1], mtab[1])         --打印结果:444  5 (默认元方法是无法查询及修改元表的!!!!!!)

二、__index元方法(基表没有的键的【元方法读】索引到【读元表】,其它情形的【元方法读写】表现为【读写基表】)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
-----------------
--『直接初始方式』
-----------------
tab  = {}                                   --基表
mtab = {k = 3}                              --元表
pmtab = {__index = mtab}                    --元表索引◆
tb = setmetatable(tab, pmtab)               --典型格式:把 mtab 设为 tab(及tb) 的元表
--tb = setmetatable(tab, {__index={k = 3}}) --另一格式:为 tab(及tb) 设置元表,与上面一样的!(★★注:后面的例子将统一使用典型格式进行表达★★)
print(pmtab.k, mtab.k)                      --打印结果:nil 3 (注:pmtab 是元表索引,没有这个k键,所以是 nil)
print(tb.k, mtab.k)                         --打印结果:3   3 (注:基表没有的键都索引到元表,但一旦键有此键时【元方法读写】表现为【读写基表】)
tb.k = 4
print(tb.k, mtab.k)                         --打印结果:4   3 (注:基表建有此键后【元方法读写】表现为【读写基表】,因为此时基表已存在这个键,所以不会索引到元表) 
print(tb.k, tab.k)                          --打印结果:4   4 (注:tb 与 tab 是等同的)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
-----------------
--『函数初始方式』
-----------------
index_key2 = function(t, k)                 --索引到元表获取(元方法)★★★★★★★★★★★★★★★★★★
	if k == "key2" then                     --注意:__index 要求函数入口为:(表、键)★★★★★★★★★★★
		return "v2"                         --              要求函数出口为:(键值)  ★★★★★★★★★★★
	else                                    --★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
		return nil
	end
end
tab  = {key1 = "v1"}                        --基表
pmtab = {__index = index_key2}              --元表索引◆
tb = setmetatable(tab, pmtab)               --典型格式:为 tab(及tb) 设置元表
print(tb.key1, tb.key2)                     --打印结果:v1  v2 (基表中`tb.key2`还没改写,所以由元方法获取,则值是"v2")
tb.key1 = 11
tb.key2 = 22                                --这里对`tb.key2`进行改写,则此时基表创建了这个键,所以不会索引到元表,下面打印的值为"22"
tb.key3 = 33
print(tb.key1, tb.key2, tb.key3)            --打印结果:11  22  33

三、__newindex元方法(基表没有的键的【元方法写(增)】索引到【写(增)元表】,其它情形的【元方法读写】表现为【读写基表】)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
-----------------
--『直接修改方式』
-----------------
tab  = {key1 = "v1"}                        --基表
mtab = {key1 = "11", key2 = "v2"}           --元表
pmtab = {__newindex = mtab}                 --元表索引◆
tb = setmetatable(tab, pmtab)               --典型格式:把 mtab 设为 tab(及tb) 的元表
print(tb.key1, tb.key2)                     --打印结果:v1   nil (注:查询不了元表)
tb.key2 = 222
print(tb.key2, mtab.key2)                   --打印结果:nil  222 (注:可以修改元表,但不能元方法读取)
tb.key3 = 333
print(tb.key3, mtab.key3)                   --打印结果:nil  333 (注:可以为元表新增键,但不能元方法读取)
tb.key1 = "v11"
print(tb.key1, mtab.key1)                   --打印结果:v11  11

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
-----------------
--『函数修改方式』
-----------------
pmtab = {}                                  --初始写(增)元表(元方法)★★★★★★★★★★★★★★★★★★
pmtab.__newindex = function(t, k, v)        --注意:__newindex 要求函数入口为:(表、键、键值) ★★★★★★
	print("....run....")                    --                 要求函数出口为:(可有可无) ★★★★★★★★
	                                        --★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
	rawset(t, k, "m-"..v)                   --绕过元方法限制,为t[k]赋值为v,并可读取,即是可读可写!!!!!!!
end
tab  = {key1 = "v1"}                        --基表
tb = setmetatable(tab, pmtab)               --典型格式:为 tab(及tb) 设置元表
print(tb.key1,  tb.key2)                    --打印结果:v1   nil
tb.key1 = "v11" tb.key2 = 2
print(tb.key1,  tb.key2)                    --打印结果:v11  m-2 (在元方法里绕过限制,直接新增表键,【初始写(增)基表】时自动加个"m-"前缀)
tb.key1 = "vvv" tb.key2 = 222
print(tb.key1,  tb.key2)                    --打印结果:vvv  222 (此时基表已存在这个'key2'键,所以第二次及之后就是【读写(增)基表】)

四、__index __newindex元方法两个应用案例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
----------------
--表只读但不能写
----------------
local function readOnly(mtb)
	local tb = {}
	local mt = {
		__index = mtb,
		__newindex = function(t, k, v)      --注意入口为 (t, k, v)
			error("别修改我!我是只读的!")
		end
	}
	setmetatable(tb, mt)
	return tb
end
local week = readOnly({"周一", "周二", "周三", "周四", "周五", "周六", "周日"});
print(week[2])                              --打印结果:周二
week[2] = "2"                               --因为表只读,当修改表时会打印出错调试信息,及脚本停止运行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
----------------
--同时读写两个表
----------------
tab  = {key1 = "v1"}                        --基表
mtab = {key1 = "11", key2 = "v2"}           --元表
pmtab = {__index = mtab, __newindex = mtab} --元表索引◆
tb = setmetatable(tab, pmtab)
print(tb.key1, tb.key2)                     --打印结果:v1  v2
tb.key2 = 22
print(tb.key2, mtab.key2)                   --打印结果:22  22 (这个表键两个表已关联,则同时修改)
tb.key1 = vv
print(tb.key1, mtab.key1)                   --打印结果:vv  11 (这个表键无法关联,只能修改基表)

五、rawget(t,k) rawset(t,k,v)绕过__index __newindex元方法的限制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-------------------------------------------------------------------------------
--`rawget(t,k)`直接读取【基表】中的实际值,而不索引到元表的`__index`元方法读取。
-------------------------------------------------------------------------------
xx = {k1 = "vv1", 2, 3}
local mt = {__index = xx}
local t = {}
setmetatable(t, mt)
print(t.k1, rawget(t, "k1"))                --打印结果:vv1  nil      (`t.k1`没改写前都是索引到元表值为`vv1`,其实际是`nil`)
t.k1 = 111
print(t.k1, rawget(t,"k1"), xx.k1)          --打印结果:111  111  vv1 (`t.k1`因为已经改写(基表建有这个键),则不再索引到元表,读写都是自己的键值)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
------------------------------------------------------------------------------------------
--`rawset(t,k,v)`直接改写(增)【基表】中的键值,而不索引到元表的`__newindex`元方法改写(增)。
------------------------------------------------------------------------------------------
xx = {k1 = "v1", k2 = "v2"}
t  = {k1 = "11"}
mt = {__newindex = xx}
print(t.k2, xx.k2)                          --打印结果:nil  v2  (没建立元方法前`t.k2`与`xx.k2`的实际值)
setmetatable(t, mt)
t.k2 = "vv2"
print(t.k2, xx.k2)                          --打印结果:nil  vv2 (元方法改写`t.k2`实际是索引至改写元表`xx.k2`)
rawset(t, "k2", "22")
print(t.k2, xx.k2)                          --打印结果:22   vv2 (绕过元方法,直接改写`t.k2`,所以`xx.k2`没变)

六、__call元方法(使用元方法把 table 转化为函数使用)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
call_concat = function(t,...)               --表转化为函数(元方法)★★★★★★★★★★★★★★★★★★★
	local s = "("                           --补充:__call  要求函数入口为:(表、可变参数)★★★★★★★★
	for k,v in pairs({...}) do              --              要求函数出口为:(可有可无)★★★★★★★★★★
		if k > 1 then                       --★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
			s = s..","                      --拼接入口参数为一串字符,使用“,”分隔开
		end
		s = s..v                            --拼接入口参数为一串字符
	end
	s = s..")"
	print(s)
end
tab  = {}                                   --基表
pmtab = {__call = call_concat}              --元表索引◆
tb = setmetatable(tab, pmtab)               --典型格式:把 mtab 设为 tab(及tb) 的元表
tb(1,2,3)                                   --运行结果:(1,2,3)
tb("a","b","c","d")                         --运行结果:(a,b,c,d)

七、__tostring元方法(使用元方法把 table 转化为字符串)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
local mt = {}
mt.__tostring = function(t)                 --表转化为字符串(元方法)★★★★★★★★★★★★★★★★★★
	local s = "{"                           --补充:__tostring  要求函数入口为:(表)★★★★★★★★★★★
	for i,v in pairs(t) do                  --                  要求函数出口为:(可有可无)★★★★★★★★
		if i > 1 then                       --★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
			s = s..","                      --拼接表员键值为一串字符,使用“,”分隔开
		end
		s = s..v                            --拼接表员键值为一串字符
	end
	s = s .."}"
	return s
end
tb = {1,2,3}
print(tb)                                   --打印结果:table: 0x24456f0
setmetatable(tb, mt)                        --将tb的元表设为mt
print(tb)                                   --打印结果:{1,2,3}

八、__add元方法(自定义两个表相加【完全自定义】,只针对数组数据,否则可能会影响结果!)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-----------------
--两个表相加-拼接
-----------------
add_concat = function(tb1, tb2)             --两表拼接操作(元方法)★★★★★★★★★★★★★★★★★★★
	local temp = {}                         --补充:__add 要求函数入口为:(表1、表2)★★★★★★★★★★★
	for k,v in pairs(tb1) do                --            要求函数出口为:(表)★★★★★★★★★★★★★★
		table.insert(temp,v)                --★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★
	end
	for k,v in pairs(tb2) do                --pairs()迭代表员(中途遇到任何键值为`nil`都跳过)
		table.insert(temp,v)
	end
	return temp
end
tab = {"a", nil, "b", "c"}                  --基表
pmtab = {__add = add_concat}                --元表索引◆
tb = setmetatable(tab, pmtab)

tb_n = {"dd", k1 = "v1", "ee", k2, "ff"}
tb = tb + tb_n                              --两个表相加!!!!!
local s = "{"
for k,v in pairs(tb) do
	s = s..v..", "                          --拼接表员键值为一串字符,使用“, ”分隔开
end
s = s.."}"
s = string.gsub(s, ", }", "}")              --字符串最后面的“, }”替换为“}”
print(s)                                    --打印结果:{a, b, c, dd, ee, ff, v1}
8、lua 对象

lua 本身没有类功能,它的类功能是通过元表来实现的。类的函数调用通过 obj:function 方式,但类的成员赋值又是通过 obj.var 方式来实现的。
lua 中面向对象实现探索-原文Lua中的面向对象

一、首先要了解一下.:在表员函数中的应用:

1
2
3
4
5
6
7
local tt = {a=1, b=2}
--函数定义--
function tt:func1(    ) self.b = 5 return self.b end  --这两个函数是完成相同的
function tt.func2(self) self.b = 5 return self.b end  --这两个函数是完成相同的
--函数调用--
print(tt:func1())                                     --这两个函数调用是相同的,打印结果:5
print(tt.func2(tt))                                   --这两个函数调用是相同的,打印结果:5

二、coco2d-x coco2d-x 游戏引擎官方给出的 class 实现(官方):
待续……

  1. 优点:
  2. 缺点:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
-- Create an class.
function class(classname, super)
    local superType = type(super)
    local cls

    if superType ~= "function" and superType ~= "table" then
        superType = nil
        super = nil
    end

    if superType == "function" or (super and super.__ctype == 1) then
        -- inherited from native C++ Object
        cls = {}

        if superType == "table" then
            -- copy fields from super
            for k,v in pairs(super) do cls[k] = v end
            cls.__create = super.__create
            cls.super    = super
        else
            cls.__create = super
        end

        cls.ctor    = function() end
        cls.__cname = classname
        cls.__ctype = 1

        function cls.new(...)
            local instance = cls.__create(...)
            -- copy fields from class to native object
            for k,v in pairs(cls) do instance[k] = v end
            instance.class = cls
            instance:ctor(...)
            return instance
        end

    else
        -- inherited from Lua Object
        if super then
            cls = clone(super)
            cls.super = super
        else
            cls = {ctor = function() end}
        end

        cls.__cname = classname
        cls.__ctype = 2 -- lua
        cls.__index = cls

        function cls.new(...)
            local instance = setmetatable({}, cls)
            instance.class = cls
            instance:ctor(...)
            return instance
        end
    end

    return cls
end

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
---------【使用方法】---------
--cls = class("类名","父类") --> 定义一个类 
--cls:ctor()                 --> 初始化函数,在创建实例的时候会自动调用
--obj = cls.new()            --> 创建实例

---------【创建基类】---------
hero = class("hero")         --定义一个基类

function hero:ctor()         --重写初始函数
	self.name = "小明"       --添加成员
	self.age  = 20           --添加成员
end

herox = hero.new()           --创建`hero`类的实例对象

print("name: "..herox.name)  --打印结果:name: 小明
print("arg : "..herox.age)   --打印结果:arg : 20

--具体使用方法参考:https://blog.51cto.com/shahdza/1566513

三、云风(吴云洋)大佬给出的 class 实现(网址):
待续……

  1. 优点:
  2. 缺点:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
local _class={}
 
function class(super)
	local class_type={}
	class_type.ctor=false
	class_type.super=super
	class_type.new=function(...) 
			local obj={}
			do
				local create
				create = function(c,...)
					if c.super then
						create(c.super,...)
					end
					if c.ctor then
						c.ctor(obj,...)
					end
				end
 
				create(class_type,...)
			end
			setmetatable(obj,{ __index=_class[class_type] })
			return obj
		end
	local vtbl={}
	_class[class_type]=vtbl
 
	setmetatable(class_type,{__newindex=
		function(t,k,v)
			vtbl[k]=v
		end
	})
 
	if super then
		setmetatable(vtbl,{__index=
			function(t,k)
				local ret=_class[super][k]
				vtbl[k]=ret
				return ret
			end
		})
	end
 
	return class_type
end
9、lua 协程

线程(thread /θred/):一个 CPU 同一时刻只能运行一段代码,但利用线程技术实现表面上看多个线程为同时执行,表象上实现并发技术,它由操作系统后台根据相应算法以隐性的方式进行切换调度。
协程(coroutine /‘kəʊruːˌtiːn/):协程的一段代码完全由主程序触发才运行,运行后再通知主程序;下一段代码以相同规则运行及通知,如此类推运行多段的代码。如果主程序根据自已的应用要求以显性的方式切换调度多个协程多段代码运行,就相当于实现了多协程并发运行。这种方式的协程以固定的调度方式并发运行,而线程则是以不确定(随机)的调度方式并发运行!
关于协程参考网文:Lua中的协程Coroutine

协程函数
功能
coroutine.create() 将某函数创建为一个协程
coroutine.resume() 主程序触发协程运行一段代码
coroutine.yield() 协程运行一段代码后通知主程序
coroutine.status() 查看指定协程的状态:dead(死亡),suspended(挂起),running(运行)
coroutine.running() 判断当前代码是否由主程序运行:true(主程序运行),false(协程运行)
coroutine.wrap () coroutine.create(),但其返回是一个操作函数来代替coroutine.resume()的使用,它并不是在保护模式下执行的,若执行崩溃会直接向外抛出

协程的创建、触发与通知相关执行逻辑及参数传递:
../img/20211221_02_08_01.jpg
注:status 值主要为 true、false(出错)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
----------------
--单个协程的运行
----------------
co = coroutine.create(function(in1, in11)                               --将某函数创建为一个协程:co
	print("1.coroutine  in:", "", in1, in11)
	local in2, in22, in222 = coroutine.yield("out1", "out11")           --协程运行第<1>段代码后通知主程序

	print("2.coroutine  in:", "", in2, in22, in222)
	local in3, in33         = coroutine.yield("out2", "out22")          --协程运行第<2>段代码后通知主程序

	print("3.coroutine  in:", "", in3, in33)
	return "out3", "out33", "out333333"                                 --协程运行第<3>段代码后通知主程序
end)

print("1.coroutine out:", coroutine.resume(co, "in1", "in11"))          --主程序触发协程运行一段代码
print("------------------------------------------------------------")
print("2.coroutine out:", coroutine.resume(co, "in2", "in22", "in222")) --主程序触发协程运行一段代码
print("------------------------------------------------------------")
print("3.coroutine out:", coroutine.resume(co, "in3"))                  --主程序触发协程运行一段代码
print("------------------------------------------------------------")
print("4.coroutine out:", coroutine.resume(co, "in4"))                  --主程序触发协程运行一段代码
print("------------------------------------------------------------")

--[[ 打印结果:
1.coroutine  in:            in1     in11
1.coroutine out:    true    out1    out11
------------------------------------------------------------
2.coroutine  in:            in2     in22     in222
2.coroutine out:    true    out2    out22
------------------------------------------------------------
3.coroutine  in:            in3     nil                      -->注释:第三次触发时,只传递了一个参数,所以协程获取第二个参数为nil
3.coroutine out:    true    out3    out33    out333333
------------------------------------------------------------
4.coroutine out:    false   cannot resume dead coroutine     -->注释:协同程序已退出,所以 coroutine.resume() 返回 false
------------------------------------------------------------
--]]
10、lua 之 table 库

一、操作函数:

说明一、关于table.xxxx()的操作,要求表中元素不应有nil的〖组员〗存在,否则会出错!

1
2
3
4
5
6
7
8
9
array = {f="fff", "bbb", h=nil, "aaa"}
#array                                    --【计算】计算表(array)中组员个数,但如果存在`nil`的组员,计算会可能出错!!!!!
table.remove(array, [n])                  --【移除】从数组移除组员。扩展参数[n]表示第几个组员位置(不写时表示移除最后一个组员)
table.insert(array, [n], value)           --【插入】向数组插入组员。扩展参数[n]表示第几个组员位置(不写时表示在尾组员后面插入)
table.sort(array)                         --【排序】排序所有的组员。注意:不支持对存在组员为`nil`的数组操作
s = table.concat(array, ["sc"], [n], [m]) --【连接】截取并连接组员。扩展参数[sc]表示插入相隔字符,[n]指定起始组员(不写时表全部组员),[m]指定结束组员(不写时表最后一个组员)

--rawset(array,k,v)                       --更改表中指定键对应的值,返回表的指针        (类同:array[k] = v)
--rawget(array,k)                         --获取表中指定键对应的值,当键不存在时返回`nil`(类同:v = array[k])

二、应用例子:

说明二、所有的table.xxxx()函数只针对〖组员〗的操作,但不会影响也不受【表员】影响!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-----【数组操作】-----
array = {f="fff", "ccc", "bbb", h=nil, "aaa"}

--连接--
print(table.concat(array))               --打印所【连接】的组员:cccbbbaaa   (截取所有组员)
print(table.concat(array,","))           --打印所【连接】的组员:ccc,bbb,aaa (截取所有组员,使用逗号隔开)
print(table.concat(array,",",2))         --打印所【连接】的组员:bbb,aaa     (截取第2到最后一个组号,使用逗号隔开)
print(table.concat(array,", ",2,3))      --打印所【连接】的组员:bbb, aaa    (截取第2到3个组号,使用逗号+空格隔开)

--排序--
table.sort(array)                        --对所有组员【排序】
print(table.concat(array,","))           --打印操作后的所有组员:aaa,bbb,ccc (截取所有组员,使用逗号隔开)

--插入--
table.insert(array,"ddd")                --在其尾部【插入】组员
print(table.concat(array,","))           --打印操作后的所有组员:aaa,bbb,ccc,ddd
table.insert(array,2,"zzz")              --指定位置【插入】组员              (★一般可选参数靠右,而本函数在中间!★)
print(table.concat(array,","))           --打印操作后的所有组员:aaa,zzz,bbb,ccc,ddd
--table.insert(tab2, 2, {"11", 22})      --二维数组插入例子:在第二行插入一行的内容 {"11", 22}
--table.insert(tab2[1], 2, "aa")         --二维数组插入例子:在第一行第二列插入内容 "aa"

--移除--
table.remove (array)                     --在其尾部【移除】组员
print(table.concat(array,","))           --打印操作后的所有组员:aaa,zzz,bbb,ccc
table.remove (array,2)                   --指定位置【移除】组员
print(table.concat(array,","))           --打印操作后的所有组员:aaa,bbb,ccc

说明三、关于我对【表员】和〖组员〗的定义:有或无《键名》的元素都称为【表员】,没有《键名》的元素称为〖组员〗。关于数组与表区别:数组其实就是一个有序的表。两个重要迭代函数如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
tab = {1, a="a", 2, b=nil, 3, nil, 4, c="c"}

---〖组员迭代函数〗---
for k,v in ipairs(tab) do                --ipairs()迭代组员(中途遇到组员值为`nil`立即结束)
	print("k="..k, v)                    --打印结果:k=1  1
end                                      --打印结果:k=2  2
                                         --打印结果:k=3  3
---【表员迭代函数】---                   
for k,v in pairs(tab) do                 --pairs()迭代表员(中途遇到任何键值为`nil`都跳过)
    print("k="..k, v)                    --打印结果:k=1  1
end                                      --打印结果:k=2  2
                                         --打印结果:k=3  3
                                         --打印结果:k=5  4
                                         --打印结果:k=c  c
                                         --打印结果:k=a  a
---【表转为字符串】--- 
function tab_to_string(tab)
	local s = "{"
	for k,v in pairs(tab) do             --使用【表员】迭代器(中途遇到任何键值为`nil`都跳过)
		if type(k) == "number" then
			s = s..v                     --拼接〖组员〗的键值为一串字符
		else
			s = s..k.."=".."\""..v.."\"" --拼接【表员】及键值为一串字符
		end
		s = s..","                       --每个元素使用“,”分隔开
	end
	s = s .."}"
	s = string.gsub(s, ",}", "}")        --去掉最后一个元素后面的“,”号
	return s
end

print(tab_to_string(tab))                --打印结果:{1,2,3,4,a="a",c="c"}
11、lua 之 string 库

一、基本语法:

说明一、字符串语法包括"" '' [[]],其中[[]]中书写的特殊字符不需要转义。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
-----【字符串】-------
s = [[a\\\"\'[[b]c]]            --多行字符串:其语法不支持转义符,不支持连续多个“]”字符,但支持单个或隔开的“]”字符。
string1 = "Lua"                 --单行字符串:""  (注:"" 与 '' 没本质区别)
string2 = '\"runoob.com\"'      --单行字符串:''  (注:"" 与 '' 没本质区别)
string3 =                       --多行字符串:[[]](注:[[ 后可直接换行,不算入多行字符串的回车或换行!)
[[
"Lua
Script"
]]

print(s)                        --打印的信息为:a\\\"\'[[b]c(本例为了说明[[]]内特殊字符不需要书写转义符)
print("string1"    .. string1)  --打印的信息为:string1Lua  (使用 .. 拼接功能)
print("\"string1:\"", string1)  --打印的信息为:"string1:"  Lua
print("string2:"    , string2)  --打印的信息为:string2:    runoob.com
print("string3:"    , string3)  --打印的信息为:string3:    "Lua
                                --       Script"
                                --       (空行)

二、操作函数:

说明二、字符操作包括 转换、查找、截取、替换、格式化。其中string.find()string.match()两函数会返回nil

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
--字符串相关转换--
string.len(s)                   --字符串(s)的长度
string.rep(s, n)                --字符串(s)重复(n)复制
string.upper(s)                 --字符串(s)转换为大写
string.lower(s)                 --字符串(s)转换为小写
string.reverse(s)               --字符串(s)左右反转
string.byte(s, [i], [j])        --字符串(s)转换为数值,默认转换第一个字符。可扩展参数[i]或[i][j],没写[i]默认为`1`,没写[j]默认等于[i]
string.char(num1, [num2],...)   --数值(num)转换为字符,单数值范围(0~255),可多个数值(num2,num3,...)
string.dump(fn, [strip])        --动态方式将函数(fn)代码打包为二进制格式并另存为文件(lua代码块),需要文件操作函数 io.write() 保存文件 和 io.read() 读取文件配合,
                                --将读取到文件的内容通过 load() 恢复为函数(变量),也可使用 loadfile(filename) 一步读出并恢复,恢复后得到函数句柄就可以操作此函数了。应用场景一般为向另一台设备传送操作函数。
--字符串位置查找--
string.find(s, ws, [i], [j])    --在字符串(s)中查找(ws),返回(ws)的头个和尾个字符位置(否则为`nil`),默认转换全部字符。可扩展参数[i]或[i][j]为查找(s)的开始与结束位置,没写[i]默认为`1`,没写[j]默认`-1`
                                --例:print(string.find("aa bb ca b", "a b")) 结果:2  4     (解析:在字符串中查找第一个"a b"所在位置)
--字符串位置截取--
string.sub(s, i, [j])           --在字符串(s)中截取,返回截取的字符串(否则为空""),默认从(i)开始截取全部。可扩展参数[j],没写[j]默认`-1`。高级应用:(i)还可以是通配符。
                                --例:print(string.sub("www.163.com", 4, -1)) 结果:.163.com (解析:从字符串第4个字符开始截取后面全部字符)
--字符串查找替换--
string.gsub(s, ws, zs, [max])   --字符串(s)中的(ws)替换为(zs),返回替换后字符串,默认全部替换。可扩展参数[max]为最多替换次数,没写[max]全部替换
                                --例:print(string.gsub("lua!aul","%a" ,"=")) 结果:===!===  (解析:将字符串中字母替换为'='符号)
--字符串查找截取--
string.match(s, ws, [i])        --在字符串(s)中查找截取(ws),返回截取的字符串(否则为`nil`),默认查找全部。可扩展参数[i]为从(s)第几个字符开始查,没写[i]默认`1`
                                --例:print(string.match("ab.txt", "%.%w+$")) 结果:.txt     (解析:截取字符中结尾为扩展名的扩展名字段)
--字符串迭代截取--
string.gmatch(s, ws)            --字符串(s)中迭代查找截取(ws),需要 for 循环语句配合
                                --例:for s in string.gmatch("a,b c", "%a+") do print(s) end (解析:类似 match(),只是查到第一个后,可接续查第二个……)
--字符格式化转换--
string.format(fs, s1, [s2],...) --字符串(fs)格式化,(s1)第一个格式化值(可数字可字符(串))。可扩展参数[s2]……,第二个……格式化值
                                --例:print(string.format("%5.3s", "monday"))  结果:  mon   (解析:截取前面3个字符并以5位右对齐方式显示)
特别备注:
s:  为普通字符串 + 转义符(\\, \', \")
ws: 为普通字符串 + 通配符 + 转义符(\\, \', \", %(, %), %[, %], %%, %$, %^, %., %?, %*, %+, %-)
fs: 为普通字符串 + 格式符 + 转义符(\\, \', \", %%)
zs: 为普通字符串 + 捕获符 + 转义符(\\, \', \", %%)(备注:为什么有 %% 转义,因为 %1 代表第一个捕获物,%2…)

特别注释:
如果普通字符串变量用到通配字符串或格式化字符串的函数参数中时,
必须自行编写处理函数对几个特殊字符增加转义符(如:`%`转义是`%%`),
例如编写函数:`string_ss_to_ws(ss)`和`string_ss_to_fs(ss)`
──点击展开例子──
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
--字符串转换--
print("163.".."com")                     --打印的结果:163.com     (--字符串【拼接】--)
print("163.", "com")                     --打印的结果:163.   com  (--字符串【拼接】--)(使用Tab分隔)
print(string.len("abc"))                 --打印的结果:3           (--字符串【长度】--)
print(string.upper("Lua abC"))           --打印的结果:LUA ABC     (--转换为【大写】--)
print(string.lower("Lua abC"))           --打印的结果:lua abc     (--转换为【小写】--)
print(string.reverse("Lua"))             --打印的结果:auL         (--字符串【反转】--)
print(string.char(97,98,99,100))         --打印的结果:abcd        (--[数值]转[字符]--)
print(string.byte("abcd",1,-1))          --打印的结果:97,98,99,100(--[字符]转[数值]--)(转换全部的字符)
print(string.byte("abcd",4))             --打印的结果:100         (--[字符]转[数值]--)(转换第四个字符)
print(string.byte("abcd"))               --打印的结果:97          (--[字符]转[数值]--)(转换第一个字符)
print(string.rep("abcd",2))              --打印的结果:abcdabcd    (--字符串【拷贝】--)(拷贝两次)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
--字符串查找--
print(string.find("hello lua Lua is Lua", "Lua"))        --打印的结果:11  13 (整块字符串查找,返回查找字符串位置-起始及结束位置(注:换行符或回车符也计入位置))
print(string.find("hello lua Lua is Lua", "Lua", 1, -1)) --打印的结果:11  13 (同上)
print(string.find("hello lua Lua is Lua", "Lua", 1))     --打印的结果:11  13 (同上)
print(string.find("hello lua Lua is Lua", "Lua", 14))    --打印的结果:18  20 (从第14个字符开始查找整块字符串,返回查找字符串位置-起始及结束位置)

--字符串截取--
print(string.sub("www.163.com", 4, 8))     --打印的结果:.163.   (--字符串【截取】--)(截取第4~8个字符)
print(string.sub("www.163.com", 4, -1))    --打印的结果:.163.com(--字符串【截取】--)(截取第4~最后个字符)
print(string.sub("www.163.com", 4))        --打印的结果:.163.com(同上)

tb = "Today is 11/28/2021 abc"
s,e = string.find(tb,"%d%d/%d%d/%d%d%d%d") --查找日期格式字符串,送到变量 s,e
if (s == nil) then
	print("return nil")                    --注意:查找不到时,不能【截取】操作,否则 lua 运行抛出异常!!!
else
	print(string.sub(tb, s, e))            --打印的结果:11/28/2021
end

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
--字符串查找替换--
print(string.gsub("abc%abc", "%%", "%%z")) --打印的结果:abc%zabc   1 (结果为"%"替换为"%z"后字符串,及替换了1次)
print(string.gsub("aAaaa","a","z"));       --打印的结果:zAzzz  4     (结果为"a"替换为"z" 后字符串,及替换了4次)
print(string.gsub("aAaaa","a","z",3));     --打印的结果:zAzza  3     (结果为"a"替换为"z" 后字符串,及替换了3次)
print(string.gsub("o, 2-Lua!", "%A", ".")) --打印的结果:o....Lua.  5 (结果为"非字母"替换为"."后字符串,及替换了5次)
print(string.gsub("o, 2-Lua!", ".", "a"))  --打印的结果:aaaaaaaaa  9 (任意字符替换为"a"后字符串,及替换了9次)

print(string.gsub("hello!lua","%a" ,"="))  --打印的结果:=====!===            8 (优先匹配 1 个字母)
print(string.gsub("hello!lua","%a?","="))  --打印的结果:=====!===            8 (从左到右优先匹配  1 个,否则最后匹配  0 个)
print(string.gsub("hello!lua","%a+","="))  --打印的结果:=!=                  2 (从左到右优先匹配最多个,否则最后匹配  1 个)
print(string.gsub("hello!lua","%a*","="))  --打印的结果:=!=                  2 (从左到右优先匹配最多个,否则最后匹配  0 个)
print(string.gsub("hello!lua","%a-","="))  --打印的结果:=h=e=l=l=o=!=l=u=a=  10(从左到右优先匹配  0 个,否则尽量匹配最少个)<- 不合常规的用法,实际不要这样用

print(string.gsub("a()b(c)d(ef)g((hij))","%(%a?%)","=")) --打印的结果:a=b=d(ef)g((hij))  2 (从左到右优先匹配  1 个,否则最后匹配  0 个)
print(string.gsub("a()b(c)d(ef)g((hij))","%(%a+%)","=")) --打印的结果:a()b=d=g(=)        3 (从左到右优先匹配最多个,否则最后匹配  1 个)
print(string.gsub("a()b(c)d(ef)g((hij))","%(%a*%)","=")) --打印的结果:a=b=d=g(=)         4 (从左到右优先匹配最多个,否则最后匹配  0 个)
print(string.gsub("a()b(c)d(ef)g((hij))","%(%a-%)","=")) --打印的结果:a=b=d=g(=)         4 (从左到右优先匹配  0 个,否则尽量匹配最少个)

print(string.gsub("a()b(c)d(ef)g((hij))","%(.?%)","="))  --打印的结果:a=b=d(ef)g((hij))  2 (从左到右优先匹配  1 个,否则最后匹配  0 个)
print(string.gsub("a()b(c)d(ef)g((hij))","%(.+%)","="))  --打印的结果:a=                 1 (从左到右优先匹配最多个,否则最后匹配  1 个)
print(string.gsub("a()b(c)d(ef)g((hij))","%(.*%)","="))  --打印的结果:a=                 1 (从左到右优先匹配最多个,否则最后匹配  0 个)
print(string.gsub("a()b(c)d(ef)g((hij))","%(.-%)","="))  --打印的结果:a=b=d=g=)          4 (从左到右优先匹配  0 个,否则尽量匹配最少个)

print(string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv))     --打印的结果:home = /home/o2ospring, user = o2ospring 2
print(string.gsub("aa bb cc dd ee ff gg hh", "(%w+)%s*(%w+)", "%2 %1"), 2) --打印的结果:bb aa dd cc ff ee hh gg 2
										   
print(string.gsub("a()","%(.+%)","="))     --打印的结果:a()  0
print(string.gsub("a()","%(.*%)","="))     --打印的结果:a=   1

v = string.gsub(v, "[%c]+$", "").."\r\n"   --假设[v]是从文件读出的一行文本,本行命令表示将一行文本的结束符替换为"回车+换行"(即为 windows 格式)
v = string.gsub(v, "^aaa", "bbb")          --假设[v]是从文件读出的一行文本,本行命令表示将一行文本的行首为"aaa"替换为"bbb"

local function string_ss_to_ws(ss)         --普通字符串变量用到通配字符串的转换函数
	ss = string.gsub(ss, "%%", "%%%%")
	ss = string.gsub(ss, "%(", "%%(")
	ss = string.gsub(ss, "%)", "%%)")
	ss = string.gsub(ss, "%[", "%%[")
	ss = string.gsub(ss, "%]", "%%]")
	ss = string.gsub(ss, "%$", "%%$")
	ss = string.gsub(ss, "%^", "%%^")
	ss = string.gsub(ss, "%.", "%%.")
	ss = string.gsub(ss, "%?", "%%?")
	ss = string.gsub(ss, "%*", "%%*")
	ss = string.gsub(ss, "%+", "%%+")
	ss = string.gsub(ss, "%-", "%%-")
	return ss
end

local function string_ss_to_zs(ss)         --普通字符串变量用到捕获字符串的转换函数
	ss = string.gsub(ss, "%%", "%%%%")
	return ss
end

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
--字符串查找截取--
print(string.match("a 2 bb 3 cc", "%d+ %a+"))    --打印的结果:2 bb("%d+ %a+"表示匹配数字段+空格+字符段)
print(string.match("a 2 bb 3 cc", "%d+ %a+", 4)) --打印的结果:3 cc(同上,但从第4个字符开始查找)

--字符迭代查找截取--
for s in string.gmatch("This,is Lua", "%a+") do  --("%a+"表示匹配多个字符,"%a"则是匹配单个字符)
	print(s)                                     --打印的结果:This(共三行内容,无空格)
end                                              --            is  (共三行内容,无空格)
                                                 --            Lua (共三行内容,无空格)

for s in string.gmatch("This,is Lua", "%a%a") do --("%a%a"表示匹配两个字符)
	print(s)                                     --打印的结果:Th  (共四行内容,无空格)
end                                              --            is  (共四行内容,无空格)
                                                 --            is  (共四行内容,无空格)
                                                 --            Lu  (共四行内容,无空格)

for s in string.gmatch("This,is Lua", "%a+,") do --("%a+,"表示匹配多个字符及逗号)
	print(s)                                     --打印的结果:This,
end

array = {}
s = "from=world, to=Lua"
for n,m in string.gmatch(s, "(%w+)=(%w+)") do    --注意 () 的用途! 简单理解为匹配的字符串中只截取括号里的字符串(有多少对括号就返回多少个参数值)
    array[n] = m
    print(n)                                     --第一次循环打印:from                   第二次循环打印:to
    print(m)                                     --第一次循环打印:world                  第二次循环打印:Lua
    print("array[\""..n.."\"]=\""..m.."\"")      --第一次循环打印:array["from"]="world"  第二次循环打印:array["to"]="Lua"
end

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
--字符格式化--
print(string.format("the value is:%d", 8)) --打印的结果:the value is:8
print(string.format("%5.3f", 13)           --打印的结果:13.00
print(string.format("%2s", "monday"))      --打印的结果:monday    (结果前面没有空格)(截取全部字符并以2位右对齐显示)(★自动扩展6位显示★)
print(string.format("%10s", "monday"))     --打印的结果:    monday(结果前面四个空格)(截取全部字符并以10位右对齐显示)
print(string.format("%5.3s", "monday"))    --打印的结果:  mon     (结果前面两个空格)(截取前面3个字符并以5位右对齐显示)
print(string.format("%-5.3s", "monday"))   --打印的结果:mon       (结果后面两个空格)(截取前面3个字符并以5位左对齐显示)

local function string_ss_to_fs(ss)         --普通字符串变量用到格式化字符串的转换函数
	ss = string.gsub(ss, "%%", "%%%%")
	return ss
end

说明三、对于使用[[]]书写的字符串、或者通过文件方式读取的内容,lua 已自动为我们加入了转义符,所以无需关心特殊字符是否要转义。

特殊字符
字符
\\ 代表一个反斜线字符:\ (可见符)
\' 代表一个单引号字符:’ (可见符)
\" 代表一个双引号字符:" (可见符)
\a 响铃
\b 退格
\f 换页
\n 换行
\r 回车
\t 水平制表(HT)(跳到下一个TAB位置)
\v 垂直制表(VT)
\0 空字符
\ddd 3 位八进制数所代表的任意字符(如:\777
\xhh 2 位十六进制所代表的任意字符(如:\xF5

说明四、关于通配字符串,在普通字符串基本上,增加通配符(如:%a、%c 等)和 增多 12 个转义符(如:%%%- 等)规则。特别说明:匹配规则有 70% 类同正则表达式,更多知识请移步《正则表达式语法》!

通配符
含义 (下面改大写表示非,例如:%A 表示非字母的字符)
%( %)
%[ %]
%^ %$
%* %+
%? %-
%. %%
匹配中一些特殊字符需要用%来转义,主要为 12 个特殊字符:( ) [ ] ^ $ * + ? - . %
%a 与任何字母配对
%u 与任何大写字母配对
%l 与任何小写字母配对
%d 与任何数字配对
%w 与任何字母 / 数字配对
%x 与任何十六进制数配对
%p 与任何标点符配对(包括:( ) [ ] % $ ^ . ? * + -
%s 与任何空白符配对(例如:空格)(注意包括Tab键
%c 与任何控制符配对(例如:\n)(注意包括Tab键
%z 与任何代表 0 的字符配对
%bxy 这里的 x 和 y 是两个明确的字符。
1、当 x ≤ y 表示起始符为 x 与 结束字符为 y 的字符串以最少个优先匹配;
2、当 x > y 表示起始符为 y 与 结束字符为 x 的字符串以最多个优先匹配。
3、补充:x y 匹配的中间可以有标点符
[字符类] 与任何有包含在 [ ] 中的字符配对,即可以自定义字符类。例如 %x 其实就是 [0-9a-fA-F]
[^字符类] 与任何不包含在 [ ] 中的字符配对,即可以自定义字符类。
( ) 用小括号括起一个子模式,这些子模式被称为捕获物。例如:(a*(.)%w(%s*))%1代表第一个捕获物a*(.)%w(%s*)%2代表第二个捕获物.%3代表第三个捕获物%s*
^ 以指定字符为【开头】(例如:^[%s]+表示有空格开头的字符串)
$ 以指定字符为【结尾】(例如:[%s]+$表示有空格结尾的字符串),同时也用来表示系统变量(例如:$HOME为用户家目录系统变量)
* 修饰符 匹配前面一个通配符n~0个(从左到右优先匹配最多个,否则最后匹配 0 个
典型应用:"%s*" 匹配空白 或 “.” 任意字符。例如:"%(%s%)" 括号间空或多个空白(如:空格)
+ 修饰符 匹配前面一个通配符n~1个(从左到右优先匹配最多个,否则最后匹配 1 个
普通例子:"%a+" 匹配连接多个字母
? 修饰符 匹配前面一个通配符1~0个(从左到右优先匹配 1 个,否则最后匹配 0 个
典型应用:"%s?" 匹配空白 或 “.?” 任意字符。例如:"%(%s?%)" 括号间空或一个空白(如:空格)
- 修饰符 匹配前面一个通配符0~n个(从左到右优先匹配 0 个,否则尽量匹配最少个
典型应用:"%s-" 匹配空白 或 “.-” 任意字符。例如:"%(%s-%)" 括号间空或多个空白(如:空格)
. 与任何字符配对(特别,不需要 % 转义符)

说明五、关于格式化字符串,在普通字符串基础上,增加格式化符(如:%d、%f 等)和 增多 1 个转义符(即:%%)规则。

格式化符
含义
%% 格式化中特殊字符“%”需要用%来转义
%c 接受一个数字, 并将其转化为ASCII码表中对应的字符
%d, %i 接受一个数字并将其转化为有符号的整数格式
%o 接受一个数字并将其转化为八进制数格式
%u 接受一个数字并将其转化为无符号整数格式
%x 接受一个数字并将其转化为十六进制数格式, 使用小写字母
%X 接受一个数字并将其转化为十六进制数格式, 使用大写字母
%e 接受一个数字并将其转化为科学记数法格式, 使用小写字母e
%E 接受一个数字并将其转化为科学记数法格式, 使用大写字母E
%f 接受一个数字并将其转化为浮点数格式
%g(%G) 接受一个数字并将其转化为%e(%E, 对应%G)及%f中较短的一种格式
%q 接受一个字符串并将其转化为可安全被Lua编译器读入的格式
%s 接受一个字符串并按照给定的参数格式化该字符串
%2x 输出2位十六进制整数(小写)….
%2X 输出2位十六进制整数(大写)….
%+d 让正数显示正号(默认情况下只有负数显示符号)
%2d 输出2位有符号十进整数, 不足2位时[前面]补空格(即:右对齐) ….其它数据格式类同
%-2d 输出2位有符号十进整数, 不足2位时[后面]补空格(即:左对齐) ….其它数据格式类同
%02d 输出2位有符号十进整数, 不足2位时[自动]补0 ……………..其它数据格式类同
%8.3f 输出宽度为8(包含小数点),小数点后面是三位。 12.34 → " 12.340"
%08.03f 输出宽度为8(包含小数点),小数点后面是三位。 12.34 → “0012.340”
%-5.3s 输出宽度为5位字符(左对齐+右边补空格)来显示截取的3位字符。

参考网文:Lua 之 string 库Lua 模式匹配Lua 正则表达式匹配Lua 字符串处理


12、lua 之 io 库

一、知识要点:

lua 对文件读操作,可以任意移动光标读取内容;但对文件写操作,只能以覆盖或追加方式写入内容,简单说就是不能直接编辑文件内容。问题来了,如何改写文件内容?【方法一】:读取一行文件内容,再写入到另一空白文件,如果中途要改写内容,则在写入前先修改缓冲内容再写入,最后所有内容写入后替换原文件(名)。【方法二】:将文件读到数组中,每行为一组员,在读/写过程中修改内容或全部读完后再修改内容,最后以覆盖方式写入文件,要求文件较小或内存足够大。估计文件编辑工具应该都是使用方法二来实现文本编辑功能。

二、操作函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
file = io.open("filename", ["mode"])  --打开文件>>>>>,返回(file)句柄(否则为`nil`)。可扩展参数[mode]表示只读、只写文件……
file:seek(["mode", n])                --设置读光标位置。可扩展参数[mode]表示当前位置、文件头位置、文件尾位置开始算起的偏移位置[n]
s = file:read(["mode"])               --读取文件的内容。可扩展参数[mode]表示当前位置往后读几个字符、或读到行尾、或读到文件尾
file:write("text")                    --在文尾写入内容。写光标的位置用户不能修改,由open()和write()函数自动设置
file:flush()                          --将写入内容存盘。
file:close()                          --关闭文件<<<<<。
io.lines("filename")                  --迭代地读取文件。需要 for 循环语句配合,【例如】:for v in io.lines("./my.lua") do print(v) end

--tostring(number)                    --将数字(数值)转为字符,方便保存到文件。
--tonumber("number")                  --将字符转为数字(数值),可用于从文件读出的文本参数转为数字进行处理。
--os.rename("old_name", "new_name")   --将文件重命名,操作成功返回:true(否则:nil 及相关错误信息)【例如】:os.rename("./f/abc.txt", "./f/123.txt")
--os.remove("file_name")              --删除一个文件,操作成功返回:true(否则:nil 及相关错误信息)补充:受到文件操作权限影响!
──点击展开例子──

一、简单模式(simple model)读写操作函数无须指出句柄:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
------------------
--打开文件读写操作
------------------
file = io.open("my.lua", "a+") --打开文件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

local i_file = io.input(file)  --【准备读】------------------------------
	print(io.read())           --读出第一行内容(默认入口参数"*l")
	print(io.read("*l"))       --读出第二行内容
	print(io.read())           --读出第三行内容
	for v in io.lines() do     --从第四行内容开始读 (io.lines 没有带参数)
		print(v)               --打印第四、五、…行内容
	end                        --读完不关闭文件(因为 io.lines 没有带参数)

local o_file = io.output(file) --【准备写】------------------------------
	io.write("\r\n--text1")    --文件未尾写入一行内容
	io.write("\r\n--text2")    --文件未尾再写入一行内容
	io.flush()                 --将写入的内容存入磁盘(否则数据可能会丢失)

io.close(file)                 --关闭文件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

------------------
--直接迭代读取内容
------------------
for v in io.lines("./my.lua") do
	print(v)                   --打印出通过迭代读取文件内容(注:一次读一行内容)
end                            --读完后自动关闭文件(注: io.lines 带参数才会自动关闭文件)

二、完全模式(complete model)使用句柄对文件进行操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
------------------
--打开文件读写操作
------------------
file = io.open("my.lua", "a+") --打开文件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

	print(file:read())         --读出第一行内容
	print(file:read("*l"))     --读出第二行内容
	for v in file:lines() do   --从第三行内容开始读 (file:lines 没有带参数)
		print(v)               --打印第三行内容
		break                  --退出,因只读1行
	end                        --读完不关闭文件(因为 file:lines 没有带参数)

	file:write("\r\n--text1")  --文件未尾写入一行内容
	file:write("\r\n--text2")  --文件未尾再写入一行内容
	file:flush()               --将写入的内容存入磁盘(否则数据可能会丢失)

	file:seek("end", -5)       --设置光标位置:文件倒数第5个位置,
	file:read()                --            再读取一行剩余内容。

file:close()                   --关闭文件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
--io.close(file)               --或者此句

------------------
--判断文件是否存在
------------------
file = io.open("my.lua", "r")  --打开文件>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

if file then                   --"r"或"r+"模式必须要判断文件是否存在
	local size = false
	size = file:seek("end")    --读出文件大小
	print("file size = "..size)

file:close()                   --关闭文件<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
else
	print("No file found!")
end

说明一、io.open(file, [“mode”]) 参数:

参数
说明
"r" 以只读+存在方式打开文件,该文件必须存在,否则返回nil。(默认的打开方式)(也常用于判断文件是否存在)
"w" 以只写+清空方式打开文件,若文件不存在则建立该文件(注:写光标不可移动)
"a" 以只写+追加方式打开文件,若文件不存在则建立该文件(注:写光标不可移动)
"r+" 以读写+覆盖方式打开文件,该文件必须存在,否则返回nil。(注:写光标可以用seek移动)
"w+" 以读写+清空方式打开文件,若文件不存在则建立该文件(注:写光标不可移动)
"a+" 以读写+追加方式打开文件,若文件不存在则建立该文件(注:写光标不可移动)
补充 1. 除追加方式写光标在文件末尾外,其它方式的读写光标都默认在文件开头处。
2. "r" "r+"必须要判断文件有存在后才可操作,否则运行会出错!
3. 关于覆盖方式,如果新写入的内容少于旧内容,会残留一些旧内容在尾部!

说明二、file:read([“mode”])、io.read([“mode”]) 参数:

参数
说明
"*l" 读出当前光标到行尾的内容,之后光标跳至下一行首(默认参数,可以不写
"*a" 读出当前光标到最后一行的内容(再"*a"模式读就会返回空白,非nil
"*n" 读出当前光标为连续数字的内容及光标移到数字后一个字符(不会跳过回车换行符),否则返回nil并且光标右移一位
数字 读出当前光标及之后共 n 个字符(其回车符+换行符共算两个字符),光标跳至 n 个字符之后
补充 只要光标在文件末尾,再读就会返回nil(但"*a"模式读返回除外)

说明三、file:seek([“mode”, n]) 参数:

参数
说明
"cur", n 从当前位置开始,n可正负数,表示光标移到前或后的第n个字符处(n 可以为 0)
"set", n 从文件前头开始,n须为正数,表示光标移到文件前头开始的第n个字符处(n ≥ 0)
"end", n 从文件末尾开始,n须为负数,表示光标移到文件末尾开始的第n个字符处(n ≤ 0)
13、lua 之 os 库

一、操作函数:

说明一、lua 提供了简单的跟操作系统有关的功能操作,还提供了直接运行系统命令,可扩展其应用功能。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
--文件操作--
os.rename("old_name", "new_name")  --将文件重命名,操作成功返回:true(否则:nil 及相关错误信息)【例如】:os.rename("./f/abc.txt", "./f/123.txt")
os.remove("file_name")             --删除一个文件,操作成功返回:true(否则:nil 及相关错误信息)补充:受到文件操作权限影响!

--系统控制--
os.getenv("os_variable")           --获取系统环境变量,操作成功返回环境变量值[字符串](否则:nil)【例如】:os.getenv("HOME")) 返回:/home/xiaoming
os.execute("cmd_string")           --运行系统字符命令,操作成功返回:true(否则:nil)【例如】:os.execute("ls -l") 列出文件(夹)信息后返回:true
os.exit(num_value)                 --退出当前lau程序,并返回状态值(默认值:0)【例如】:os.exit(0) 退出 lau 程序

--时间操作--
t = os.time([day_time_tb])         --获取系统当前时间戳,可用扩展参数[day_time_tb]指定日期时间的时间戳。【例如】:os.time() 返回:1641384000。[day_time_tb]表格例子:{year=2020, month=12, day=31, hour=5, min=6, sec=7, isdst=false}(isdst是否为夏令时))
os.difftime(t2, t1)                --计算两时间戳的差值
os.date(["fmt"], [t])              --获取当前的日期时间,可用扩展参数[fmt]格式化返回的日期时间,[t]指定时间戳的日期时间。【例如】:os.date() 返回:Wed Jan  5 21:57:19 2022。{[fmt]:%a 星期几简写(如:Wed);%Y……}
os.clock()                         --获取CPU运行时间戳,常用于计算一段代码运行的时间。【例如】:os.clock() 返回:0.00091
──点击展开例子──
1
2
3
4
5
6
7
8
9
------------------
--更改文件名称
------------------
print(os.rename("./oldname.lua", "./newname.lua")) --如果操作成功则打印:true (否则:nil 及相关错误信息)

------------------
--删除文件(夹)
------------------
print(os.remove("./aaa.lua"))                      --如果操作成功则打印:true (否则:nil 及相关错误信息)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
------------------
--获取系统环境变量
------------------
print(os.getenv("HOME"))    --打印结果:/home/xiaoming
print(os.getenv("ROOT"))    --打印结果:nil (不存在此环境变量)
--[[
  获取系统哪种环境变量,与系统直接有关,
  例如:linux 系统,"HOME"表获取家目录。
--]]

------------------
--运行一条系统命令
------------------
os.execute("mkdir ./tmp/d") --运行系统命令:创建一个文件夹 (备注:正确返回true,否则返回nil)
os.execute("touch ./tmp/f") --运行系统命令:创建一个文件
os.execute("rm ./xxxx.lua") --运行系统命令:删除一个文件

------------------
--退出当前lau程序
------------------
os.exit(0)                  --退出当前lau程序,并返回状态值(默认值:0)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
----------------
--获取日期时间戳
----------------
print(os.time())                                           --获取系统当前时间戳:1641384000
print(os.time{year=2022, month=1, day=5})                  --获取指定日期时间戳:1641355200
print(os.time{year=2022, month=1, day=5, hour=21, min=32}) --获取指定日期时间戳:1641389520

local t1 = os.time({year=2022, month=1, day=5})
local t2 = os.time({year=2022, month=1, day=5, hour=21, min=32})
print(os.difftime(t2, t1))                                 --获取两个时间戳的差值:34320.0
--[[
  1.{year=年, month=月, day=日, hour=时, min=分, sec=秒, isdst=夏令时}
  2.其中 year、month、day 三个字段是必须的,其它字段默认取 12:00:00
  3.isdst 为布尔值,true 表示夏令时
--]]

----------------
--格式化日期时间
----------------
print(os.date())                       --打印结果:Wed Jan  5 21:57:19 2022
print(os.date("today is %A, in %B"))   --打印结果:today is Wednesday, in January
print(os.date("%x", os.time()))        --打印结果:01/05/22

tb = os.date("*t")                     --打印结果:sec    41     (秒)
for k,v in pairs(tb) do                --打印结果:year   2022   (年)
    print(k,v)                         --打印结果:isdst  false  (非夏令时)
end                                    --打印结果:day    5      (日)
                                       --打印结果:yday   5
                                       --打印结果:hour   14     (时)
                                       --打印结果:wday   4      (周三)
                                       --打印结果:month  1      (月)
                                       --打印结果:min    48     (分)
------------------
--CPU时间戳
------------------
local x = os.clock()
local s = 0
for i=1,10^7 do s = s + i end
print(type(x), x)                                        --打印结果:number  0.002064
print(string.format("run time: %.2f\n", os.clock() - x)) --打印整段代码运行时间:run time: 0.07 (秒)

说明二、关于日期时间显示方式,可以通过格式化参数制定显示格式。

格式 说明
%a 星期简写,如:Wed
%A 星期全称,如:Wednesday
%b 月份简写,如:Sep
%B 月份全称,如:September
%c 日期和时间,如:01/05/22 13:43:08
%d 一个月中的第几天,01-31
%H 24小时制中的小时数,00-23
%I 12小时制中的小时数,01-12
%j 一年中的第几天,001-366
%M 分钟数,00-59
%m 月份数,01-12
%p 上午(am)或下午(pm)
%S 秒数,00-59
%w 一星期中的第几天,0-6
%x 日期,如:09/16/22
%X 时间,如:13:47:20
%y 两位数的年份,如:22
%Y 完整的年份,如:2022
%% 字符%

参考网文:Lua 之 os 库


14、lua 之 math 库

一、操作函数:

注意:使用math.random之前要调用math.randomseed(os.time())设置随机数种子一次(当然中途可以多次修改随机数种子),否则得不出你想要的结果!

公式 公式描述 计算例子 计算结果
pi 圆周率常量 print(math.pi) 3.1415926535898
abs 取绝对值 math.abs(-15) 15
ceil 向上取整 math.ceil(5.8) 6
floor 向下取整 math.floor(5.6) 5
max 取参数最大值 math.max(2.71,100,-98,23) 100
min 取参数最小值 math.min(2.71,100,-98,23) -98
modf 取整数和小数部分 math.modf(15.98) 15 98
randomseed 设置随机数种子 math.randomseed(os.time()) 在使用math.random函数之前必用本函数设置随机数种子
random 获取随机数 math.random(1,100)
math.random(100)
获取1-100的随机数
fmod(mod) 取模运算 math.fmod(14,5) 4
sin 正弦函数 math.sin(math.rad(30)) 0.5
cos 余弦函数 math.cos(0.5) 0.87758256
tan 正切函数 math.tan(0.5) 0.5463024
asin 反正弦函数 math.asin(0.5) 0.52359877
acos 反余弦函数 math.acos(0.5) 1.04719755
atan 反正切函数 math.atan(0.5) 0.463647609
atan2 x/y的反正切值 math.atan2(90.0,45.0) 1.10714871
sinh 双曲线正弦函数 math.sinh(0.5) 0.5210953
cosh 双曲线余弦函数 math.cosh(0.5) 1.276259652
tanh 双曲线正切函数 math.tanh(0.5) 0.46211715
deg 弧度转角度 math.deg(math.pi) 180
rad 角度转弧度 math.rad(180) 3.14159265358
frexp 分解v=x*(2的n次方) math.frexp(10.0) 0.625 4
ldexp 计算v*(2的n次方) math.ldexp(10.0,3) 80=10*(2^3)
exp 计算以e的x次方 math.exp(2) 2.718281828
pow 计算以x的y次方 math.pow(2,5) 32
sqrt 开平方 math.sqrt(16) 4
log10 计算以10为基数的对数 math.log10(100) 2
log 计算一个数的自然对数 math.log(2.71) 0.9969
15、lua 调试

一、操作函数:

1
2
3
4
print(...)                           --【打印】打印函数
assert(v, ["message"])               --【断言】断言函数,当(v)值为假(nil 或 false)时输出[message]信息,如果不指出则默认输出信息:"assertion failed!" 
error("message", [level])            --【错误】错误函数,输出错误信息后强制结束lua运行,扩展参数[level]指出错误信息的深度级别(默认值:1)
debug.debug()                        --【交互】进入交互,相当于暂停程序,此时可输入诸如`print()`查看一些变量的值,当输入`cont`后退出交互模式并继续运行程序

二、应用例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
--函数入口参数断言--
local function add(a, b)
	assert(type(a) == "number", "[a] It's not a number")
	assert(type(b) == "number", "[b] It's not a number")
	return a + b
end

add(10, "5")      --打印的错误信息:
                  --lua: test.lua:4: [b] It's not a number
                  --stack traceback:
                  --        [C]: in function 'assert'
                  --        test.lua:4: in local 'add'
                  --        test.lua:7: in main chunk
                  --        [C]: in ?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
--运行过程抛出错误--
local function add(a, b)
	if type(a) ~= "number" then
		error("[a] It's not a number", 1)
	end
	if type(b) ~= "number" then
		error("[b] It's not a number", 1)
	end
	return a + b
end

add(10, "5")      --打印的错误信息:
                  --lua: test.lua:7: [b] It's not a number
                  --stack traceback:
                  --        [C]: in function 'error'
                  --        test.lua:7: in local 'add'
                  --        test.lua:11: in main chunk
                  --        [C]: in ?
错误信息的深度:
error("message", [level])
Level = 0: 不添加错误位置信息
Level = 1: 指出调用错误位置(文件+行号)(默认)
Level = 2: 同上,但比上面多一层级调用信息
Level = ……

待续……
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
debug.debug()
debug.gethook([thread])
debug.getinfo([thread], f, [what])
debug.getlocal([thread], f, local)
debug.getmetatable(value)
debug.getregistry()
debug.getupvalue(f, up)
debug.getuservalue(object)
debug.sethook([thread], hook, mask, [count])
debug.setlocal([thread], level, local, value)
debug.setmetatable(value, table)
debug.setupvalue(f, up, value)
debug.setuservalue(udata, value)
debug.traceback([thread], [message, [level]])
debug.upvalueid(f, n)
debug.upvaluejoin(f1, n1, f2, n2)

16、lua 垃圾回收

待续……

Lua 数据库

待续……

Lua 应用

1、Lua 4 个不同场景的例子

待续……