参考资料
The Programming Language Lua
Lua 教程 | 菜鸟教程 (runoob.com)
LuatOS 文档
Lua 简介
Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
Lua 是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组于 1993 年开发的,该小组成员有:Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo。
设计目的
其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
一般来说,我们可以使用 C/C++ 来实现功能,而用 Lua 来实现业务逻辑,这样就可以让我们的程序保持高效的同时,又能灵活快速地实现我们的业务。
Lua 特性
- 轻量级: 它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
- 可扩展: Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是本来就内置的功能一样。
- 其它特性:
- 支持面向过程(procedure-oriented)编程和函数式编程(functional programming);
- 自动内存管理;只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
- 语言内置模式匹配;闭包(closure);函数也可以看做一个值;提供多线程(协同进程,并非操作系统所支持的线程)支持;
- 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
应用场景
- 游戏开发
- 独立应用开发
- Web 应用开发
- 扩展和数据库插件如:MySQL Proxy 和 MySQL WorkBench
- 安全系统,如入侵检测系统
环境安装及运行
Windows
Github 下载 github.com/rjpcomputin…
麻瓜式安装即可,不过只有最新只有 5.1
版本的,有些新特性会不支持。
自行编译安装:
lua-5.4.6.tar.gz
D:lua_buildlua-5.4.6
创建 build.bat
内容见后文set lua_version=5.4.6
为你自己的版本set compiler_bin_dir=C:MinGW-w64mingw64bin
为你自己的 MinGW
的 bin
目录build.bat
lua
目录,就是成功了,然后把 lua
的 bin
目录配置到环境变量 PATH
里面,就可以使用了@echo off
:: ========================
:: file build.cmd
:: ========================
setlocal
:: you may change the following variable's value
:: to suit the downloaded version
set lua_version=5.4.6
set work_dir=%~dp0
:: Removes trailing backslash
:: to enhance readability in the following steps
set work_dir=%work_dir:~0,-1%
set lua_install_dir=%work_dir%lua
set compiler_bin_dir=C:MinGW-w64mingw64bin
set lua_build_dir=%work_dir%lua-%lua_version%
cd /D %lua_build_dir%
mingw32-make PLAT=mingw
echo.
echo **** COMPILATION TERMINATED ****
echo.
echo **** BUILDING BINARY DISTRIBUTION ****
echo.
:: create a clean "binary" installation
mkdir %lua_install_dir%
mkdir %lua_install_dir%doc
mkdir %lua_install_dir%bin
mkdir %lua_install_dir%include
copy %lua_build_dir%doc*.* %lua_install_dir%doc*.*
copy %lua_build_dir%src*.exe %lua_install_dir%bin*.*
copy %lua_build_dir%src*.dll %lua_install_dir%bin*.*
copy %lua_build_dir%srcluaconf.h %lua_install_dir%include*.*
copy %lua_build_dir%srclua.h %lua_install_dir%include*.*
copy %lua_build_dir%srclualib.h %lua_install_dir%include*.*
copy %lua_build_dir%srclauxlib.h %lua_install_dir%include*.*
copy %lua_build_dir%srclua.hpp %lua_install_dir%include*.*
echo.
echo **** BINARY DISTRIBUTION BUILT ****
echo.
%lua_install_dir%binlua.exe -e"print [[Hello!]];print[[Simple Lua test successful!!!]]"
echo.
pause
Mac
curl -R -O http://www.lua.org/ftp/lua-5.4.6.tar.gz
tar zxf lua-5.4.6.tar.gz
cd lua-5.4.6
make macos test
make install
Linux
curl -R -O http://www.lua.org/ftp/lua-5.4.6.tar.gz
tar zxf lua-5.4.6.tar.gz
cd lua-5.4.6
make linux test
make install
在线环境
LuatOS 在线模拟 - lua在线测试
基本语法
交互式编程
Lua
交互式编程模式可以通过命令 lua -i
或 lua
来启用:
lua -i
Lua 5.4.6 Copyright (C) 1994-2023 Lua.org, PUC-Rio
脚本式编程
我们可以创建一个文件 test.lua
,输入:
print("Hello World!")
然后执行下面脚本运行:
lua test.lua
打印中文的时候如果出现乱码,VSCode 里面 Save with Encoding 可以改为 GBK。
编译
使用 luac
将 .lua
编译成字节码,这些字节码可以直接在 Lua
虚拟机上运行,执行效率也更高。
基本语法:
luac [ options ] [ file ... ]
luac test.lua
执行之后将会生成一个 luac.out(这是输出文件的默认名称)
文件。
luac
的常用选项:
- -o file:指定编译输出的文件名
- -c:只进行预处理而不进行编译
- -s:对源代码进行优化
- -p:预处理源代码但不进行编译优化
luac -o test.luac teat.lua
执行之后将会生成一个 test.luac
文件。
编译之后执行方法还是一样的,用 lua
命令即可。
注释
-- 单行注释用两个减号
--[[
多行注释
多行注释
--]]
变量
变量在使用前,需要在代码中进行声明,即创建该变量。
编译程序执行代码之前编译器需要知道如何给语句变量开辟存储区,用于存储变量的值。
变量类型
Lua 变量有三种类型:全局变量、局部变量、表中的域。
Lua 中的变量全是全局变量,哪怕是语句块或是函数里,除非用 local 显式声明为局部变量。
全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil。
print(b)
nil
b = 10
print(b)
10
如果想要删除全局变量,将其赋值为 nil
即可。
b = nil
局部变量的作用域为从声明位置开始到所在语句块结束。
变量的默认值均为 nil。
a = 5 -- 全局变量
local b = 5 -- 局部变量
function joke()
c = 5 -- 全局变量
local d = 6 -- 局部变量
end
joke()
print(c, d) --> 5 nil
do
local a = 6 -- 局部变量
b = 6 -- 对局部变量重新赋值
print(a, b); --> 6 6
end
print(a, b) --> 5 6
赋值语句
Lua 可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。
a, b = 10, 2
print(a, b) -- 10 2
利用上述特性进行值交换,或将函数调用返回给变量:
x, y = y, x -- swap 'x' for 'y'
a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[j]'
a, b = f() -- f()返回两个值,第一个赋给a,第二个赋给b
应该尽可能的使用局部变量,有两个好处:
避免命名冲突。
访问局部变量的速度比全局变量更快。
索引
对 table 的索引使用方括号 []
。Lua 也提供了 .
操作。
local myArr = {}
myArr["key1"] = "value1"
myArr["key2"] = "value2"
print(myArr["key1"])
print(myArr.key2)
输出:
value1
value2
数据类型
Lua
是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。
8种基础类型如下表所示:
数据类型 | 描述 |
---|---|
nil | 空,表示无效值,在条件表达式中相当于false |
boolean | 布尔值:true和false |
number | 数字类型,表示双精度类型的浮点数 |
string | 字符串,单引号或双引号来表示 |
function | 由C或Lua编写的函数 |
userdata | 表示任意存储在变量中的C数据结构 |
thread | 表示执行的独立线路,用于执行协同程序 |
table | 表,其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 |
Lua
提供一个 type
函数来检测变量或值的类型:
print(type("Hello world"))
print(type(10.4 * 3))
print(type(print))
print(type(type))
print(type(true))
print(type(nil))
print(type(type(X)))
输出:
string
number
function
function
boolean
nil
string
nil
nil
类型表示空值,给全局变量赋值 nil
等同于把它们删除。
nil
作比较时应该加上引号:
if (type(nil)==nil) then
print(true)
else
print(false)
end
if (type(nil)=="nil") then
print(true)
else
print(false)
end
输出:
false
true
boolean
布尔类型,Lua
中只把 false
和 nil
看作假,数字的 0 是真,这与 Javascript
等语言是不一样的。
number
默认只有一种数字类型,double 类型。
string
字符串,可以用单引号,也可以用双引号来表示。
string1 = "this is string1"
string2 = 'this is string2'
字符串块的表示可以使用 [[]]
:
html = [[
Hello World
]]
print(html)
输出:
Hello World
字符串拼接使用 ..
,而不是 +
,+
是用于数字类型的:
print("a" .. 'b')
print(157 .. 428)
print("2" + 6)
print("2" + "6")
输出:
ab
157428
8
8
print("abc" + 1) -- 会报错
使用 #
来获取字符串的长度:
len = "string len"
print(#len)
print(#"string len")
输出:
10
10
table
表,也是关联数组,索引可以是数字或者字符串。
-- 创建一个空的 table
local tbl1 = {}
-- 直接初始表
local tbl2 = {"apple", "pear", "orange", "grape"}
print(tbl1)
print(tbl2[1]) -- 注意不同于其他语言的数组把 0 作为数组的初始索引,在 Lua 里表的默认初始索引一般以 1 开始。
输出:
table: 000002109d63a0c0
apple
local a = {}
a["key"] = "value"
a[10] = 11
for k, v in pairs(a) do
print(k .. " : " .. v)
end
输出:
key : value
10 : 11
table 不会固定长度大小,有新数据添加时 table 长度会自动增长,没初始的 table 元素 都是 nil
。
local a3 = {}
for i = 1, 10 do
a3[i] = i
end
a3["key"] = "val"
print(a3["key"])
print(a3[1])
print(a3[10])
print(a3["none"])
输出:
val
1
10
nil
function
函数是被看作是"第一类值(First-Class Value)",即函数和变量之间没有区别,函数可以存在变量里。
local function factorial1(n)
if n == 0 then
return 1
else
return n * factorial1(n - 1)
end
end
print(factorial1(5))
local factorial2 = factorial1
print(factorial2(5))
输出:
120
120
function 可以以匿名函数(anonymous function)的方式通过参数传递:
local function testFun(tab, fun)
for k, v in pairs(tab) do
print(fun(k, v));
end
end
local tab = { key1 = "val1", key2 = "val2" };
testFun(tab,
function(key, val) --匿名函数
return key .. "=" .. val;
end
);
输出:
key1=val1
key2=val2
thread
在 Lua
里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。
userdata
userdata
是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct
和 指针)存储到 Lua
变量中调用。
流程控制
if 语句
--[ 定义变量 --]
local a = 10;
--[ 使用 if 语句 --]
if (a < 20)
then
--[ if 条件为 true 时打印以下信息 --]
print("a 小于 20");
end
print("a 的值为:", a);
输出:
a 小于 20
a 的值为: 10
if...else 语句
--[ 定义变量 --]
local a = 100;
--[ 检查条件 --]
if (a < 20)
then
--[ if 条件为 true 时执行该语句块 --]
print("a 小于 20")
else
--[ if 条件为 false 时执行该语句块 --]
print("a 大于 20")
end
print("a 的值为 :", a)
输出:
a 大于 20
a 的值为 : 100
if...elseif...else 语句
--[ 定义变量 --]
local a = 100
--[ 检查布尔条件 --]
if (a == 10)
then
--[ 如果条件为 true 打印以下信息 --]
print("a 的值为 10")
elseif (a == 20)
then
--[ if else if 条件为 true 时打印以下信息 --]
print("a 的值为 20")
elseif (a == 30)
then
--[ if else if condition 条件为 true 时打印以下信息 --]
print("a 的值为 30")
else
--[ 以上条件语句没有一个为 true 时打印以下信息 --]
print("没有匹配 a 的值")
end
print("a 的真实值为: ", a)
输出:
没有匹配 a 的值
a 的真实值为: 100
循环
while 循环
local a = 10
while (a < 20)
do
print("value of a:", a)
a = a + 1
end
输出:
value of a: 10
value of a: 11
value of a: 12
value of a: 13
value of a: 14
value of a: 15
value of a: 16
value of a: 17
value of a: 18
value of a: 19
for 循环
数值for循环
for var=exp1,exp2,exp3 do
end
var
从 exp1
变化到 exp2
,每次变化以 exp3
为步长递增,并执行一次 "执行体"。exp3
是可选的,如果不指定,默认为1。
for i = 10, 1, -1 do
print(i)
end
输出:
10
9
8
7
6
5
4
3
2
1
泛型for循环
local a = { "one", "two", "three" }
for i, v in ipairs(a) do
print(i, v)
end
输出:
1 one
2 two
3 three
ipairs
是 Lua
提供的一个迭代器函数,用了迭代数组
repeat...until循环
repeat
statements
until( condition )
先执行循环体的语句,再进行条件判断,如果条件为 flase 则继续执行循环体,否则跳出循环。
local a = 10
repeat
print("value of a:", a)
a = a + 1
until (a > 15)
输出:
value of a: 10
value of a: 11
value of a: 12
value of a: 13
value of a: 14
value of a: 15
循环控制语句
break语句
local a = 10
while(a 15) then
break
end
end
执行到 break
处会跳出循环。
goto语句(5.2+的特性)
语法:
goto Label
定义 Label:
::Label::
local a = 1
::label::
print("--- goto label ---")
a = a + 1
if a < 3 then
goto label -- a 小于 3 的时候跳转到标签 label
end
输出:
--- goto label ---
--- goto label ---
如果 Label
后面需要接多条语句可以用 do...end
:
local a = 1
::label::
do
print("--- goto label1 ---")
print("--- goto label2 ---")
end
a = a + 1
if a < 3 then
goto label -- a 小于 3 的时候跳转到标签 label
end
输出:
--- goto label1 ---
--- goto label2 ---
--- goto label1 ---
--- goto label2 ---
goto
语句经常被用来实现 continue
功能:
for i = 1, 3 do
if i {...} 表示一个由所有变长参数构成的数组
s = s + v
end
return s
end
print(add(3, 4, 5, 6, 7)) --> 25
我们可以将可变参数赋值给一个变量,#arg
、select("#", ...)
或 select(n, ...)
三种方式都可以获取可变参数的数量:
local function average(...)
local result = 0
local arg = { ... } --> arg 为一个表,局部变量
for i, v in ipairs(arg) do
result = result + v
end
print(#arg) --> 6
print(select("#", ...)) --> 6
-- 用于返回从起点 n 开始到结束位置的所有参数列表
print(select(3, ...)) --> 3 4 5 6
return result / #arg
end
print(average(10, 5, 3, 4, 5, 6)) --> 5.5
有时候我们可能需要几个固定参数加上可变参数,固定参数必须放在变长参数之前:
local function fwrite(fmt, ...) ---> 固定的参数fmt
return io.write(string.format(fmt, ...))
end
fwrite("abcdefgn") -->fmt = "abcdefg", 没有变长参数。
fwrite("%d%dn", 1, 2) -->fmt = "%d%d", 变长参数为 1 和 2
内置函数
Lua
也有很多内置函数,我们可以通过官方手册进行查询:Lua 5.4 Reference Manual - contents
我这里是用的 5.4 版本的:
迭代器
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。在 Lua 中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。
在泛型 for 循环中就用到了迭代器,ipairs
就是 Lua
默认提供的迭代函数。
下面是一个实例:
local array = { "Google", "Runoob" }
for key, value in ipairs(array)
do
print(key, value)
end
输出:
1 Google
2 Runoob
下面我们来分析一下泛型 for 的执行过程:
ipairs(array)
会返回迭代函数、array、0);与多值赋值一样,如果表达式返回的结果个数不足三个会自动用 nil 补足,多出部分会被忽略。在 Lua
中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。Lua
的迭代器包含以下两种类型:
- 无状态的迭代器
- 多状态的迭代器
无状态的迭代器
无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。
这种无状态迭代器的典型的简单的例子是 ipairs
,它遍历数组的每一个元素((1, t[1], (2, t[2]) ...),元素的索引需要是数值。
以下实例我们使用了一个简单的函数来实现迭代器,实现 数字 n 的平方:
local function square(iteratorMaxCount, currentNumber)
if currentNumber < iteratorMaxCount
then
currentNumber = currentNumber + 1
return currentNumber, currentNumber * currentNumber
end
end
-- 3 就是状态常量,0就是控制变量
for i, n in square, 3, 0
do
print(i, n)
end
输出:
1 1
2 4
3 9
迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标(控制变量),ipairs
和迭代函数都很简单,我们在 Lua 中可以这样实现:
local function iter(a, i)
i = i + 1
local v = a[i]
if v then
return i, v
end
end
function ipairs(a)
return iter, a, 0
end
当 Lua
调用 ipairs(a)
开始循环时,他获取三个值:迭代函数 iter
、状态常量 a
、控制变量初始值 0
;然后 Lua
调用 iter(a, 0)
返回 1, a[1]
(除非 a[1] = nil);第二次迭代调用 iter(a,1)
返回 2, a[2]
…… 直到第一个 nil
元素。
多状态的迭代器
很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到 table 内,将 table 作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在 table 内,所以迭代函数通常不需要第二个参数。
local array = { "Google", "Runoob" }
local function elementIterator(collection)
local index = 0
local count = #collection
-- 闭包函数
return function()
index = index + 1
if index =5.3)5//2 输出结果 2
关系运算符
设定 A 的值为10,B 的值为 20
操作符 描述 实例 == 等于,检测两个值是否相等,相等返回 true,否则返回 false (A == B) 为 false。 ~= 不等于,检测两个值是否相等,不相等返回 true,否则返回 false (A ~= B) 为 true。 大于,如果左边的值大于右边的值,返回 true,否则返回 false (A > B) 为 false。 = 大于等于,如果左边的值大于等于右边的值,返回 true,否则返回 false (A >= B) 返回 false。 3 nil
local t2 = setmetatable({ bar = 1 }, { __index = other })
print(t2.foo, t2.bar) --> 3 1
local mytable = setmetatable({ key1 = "value1" }, {
__index = function(mytable, key)
if key == "key2" then
return "metatablevalue"
else
return nil
end
end
})
print(mytable.key1, mytable.key2) --> value1 metatablevalue
__newindex 元方法
__newindex
元方法用来对表更新,__index
则用来对表访问 。
当你给表的一个缺少的索引赋值,解释器就会查找 __newindex
元方法:如果存在则调用这个函数而不进行赋值操作。
以下实例演示了 __newindex
元方法的应用
local mymetatable = {}
local mytable = setmetatable({ key1 = "value1" }, { __newindex = mymetatable })
print(mytable.key1) --> value1
mytable.newkey = "新值2"
print(mytable.newkey, mymetatable.newkey) --> nil 新值2
mytable.key1 = "新值1"
print(mytable.key1, mymetatable.key1) --> 新值1 nil
正常情况下,给 newkey
赋值是能正常赋值进去的,但是这里却没有赋值进去,原因就是被 __newindex
拦截了,对已存在的 key1
还是可以正常赋值。
local mytable = setmetatable({ key1 = "value1" }, {
__newindex = function(mytable, key, value)
rawset(mytable, key, """ .. value .. """)
end
})
mytable.key1 = "new value"
mytable.key2 = 4
print(mytable.key1, mytable.key2) --> new value "4"
操作符相关元方法
两表相加操作:
-- 计算表中最大值,table.maxn在Lua5.2以上版本中已无法使用
-- 自定义计算表中最大键值函数 table_maxn,即返回表最大键值
local function table_maxn(t)
local mn = 0
for k, v in pairs(t) do
if mn < k then
mn = k
end
end
return mn
end
-- 两表相加操作
local mytable = setmetatable({ 1, 2, 3 }, {
__add = function(mytable, newtable)
for i = 1, table_maxn(newtable) do
table.insert(mytable, table_maxn(mytable) + 1, newtable[i])
end
return mytable
end
})
local secondtable = { 4, 5, 6 }
mytable = mytable + secondtable
for k, v in ipairs(mytable) do
print(k, v)
end
其它操作符对应的元方法:
模式
描述
__add
对应的运算符 '+'.
__sub
对应的运算符 '-'.
__mul
对应的运算符 '*'.
__div
对应的运算符 '/'.
__mod
对应的运算符 '%'.
__unm
对应的运算符 '-'.
__concat
对应的运算符 '..'.
__eq
对应的运算符 '=='.
__lt
对应的运算符 '