本文参考 菜鸟教程
目录
Lua 简介
- Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能
环境安装
Lua 安装可参考 CSDN 直接在 Lua 官网 下载,Windows 和 Linux 都有提供~
- CentOS 7 安装可参考 CSDN
IDE 使用 VSCode,安装下列插件 (一个用于调试,一个用于代码补全):
- 要想进行调试需要配置
launch.json
:
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Lua Debug", // 名字,可以随意更改
"type": "lua", // 要调试的语言类型
"request": "launch", // launch模式,等同于使用lua.exe来执行你的代码
"program": "${workspaceFolder}/test.lua", // lua.exe执行的入口文件
"cwd": "${workspaceFolder}", // lua.exe的当前目录
"stopOnEntry": false, // 开始调试时是否先暂停
"consoleCoding": "utf8",// lua的标准输出的编码
"path": "${workspaceFolder}/?.lua", // 初始化package.path
"cpath": "${workspaceFolder}/?.dll", // 初始化package.cpath
"arg": [] // lua.exe 的命令行参数
}
]
}
更多配置参考:GitHub
上面的 Debug 插件在 Windows 下是好用的,但是我到 Linux 下用的时候它就是不好使… 崩溃。好在后来又找到了这个插件:
配置更简单,直接在设置里填写启动 Lua 解释器的命令即可:
这个插件也很有特点,Linux 下好用 Windows 下就是不好用。也很有可能是我操作有问题。目前作为新手就只能先在 Windows 和 Linux 下分别用这两款插件了… (菜鸡的左右横跳)
Lua 基础
运行 lua 程序
- 可以直接使用解释器进行交互式编程
- 也可以编写
.lua
文件进行脚本式编程
$ lua hello.lua
注释
- 单行注释:两个减号
--
- 多行注释: Multiple line comments start with double hyphen and two square brackets. and end with two square brackets.
--[[
print('Hello World')
]]
- 多行注释多加一个
-
后就相当于取消注释
---[[
print('Hello World')
]]
关键字
- Lua reserved words are:
and
,break
,do
,else
,elseif
,end
,false
,for
,function
,if
,in
,local
,nil
,not
,or
,repeat
,return
,then
,true
,until
,while
. - 一般约定,以下划线开头连接一串大写字母的名字(比如
_VERSION
)被保留用于 Lua 内部全局变量
print(_VERSION) -- output: Lua 5.1
变量
- Lua 变量有三种类型:全局变量、局部变量、表中的域
- 默认情况下,变量总是认为是全局的,那怕是语句块或是函数里,除非用
local
显式声明为局部变量 - 变量的默认值均为
nil
- 默认情况下,变量总是认为是全局的,那怕是语句块或是函数里,除非用
应该尽可能的使用局部变量,1. 避免命名冲突;2. 访问局部变量的速度比全局变量更快
- 全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:
nil
- 如果你想删除一个全局变量,只需要将变量赋值为
nil
。也就是说,只要变量不等于nil
,这个变量就存在 - Variables hold values which have types, variables don’t have types.
- 这点跟 python 很像
赋值语句
- Lua 可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量
a, b = 10, 2 * x --> a=10; b=2*x
- 遇到赋值语句 Lua 会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值:
x, y = y, x -- swap 'x' for 'y'
- 当变量个数和值的个数不一致时,Lua 会一直以变量个数为基础采取以下策略:
- 变量个数
>
>
> 值的个数: 按变量个数补足
nil
- 变量个数 < < < 值的个数: 多余的值会被忽略
- 变量个数
>
>
> 值的个数: 按变量个数补足
a, b, c = 0, 1
print(a, b, c) --> 0 1 nil
a, b = a+1, b+1, b+2 -- value of b+2 is ignored
print(a, b) --> 1 2
a, b, c = 0
print(a, b, c) --> 0 nil nil
- 多值赋值经常用来交换变量,或将函数调用返回给变量:
a, b = f()
Lua 数据类型
- Lua 中有 8 个基本类型:
nil
、boolean
、number
、string
、userdata
、function
、thread
和table
- 我们可以使用
type
函数测试给定变量或者值的类型:
a = 1
b = "abc"
c = {}
d = print
print(type(a)) --> number
print(type(b)) --> string
print(type(c)) --> table
print(type(d)) --> function
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(X))) --> string
nil
nil
类型表示一个无效值(在条件表达式中相当于false
)
> print(type(a)) -- 打印一个没有赋值的变量
nil
- 给全局变量或者
table
表里的变量赋一个nil
值,等同于把它们删掉
tab1 = { key1 = "val1", key2 = "val2", "val3" }
for k, v in pairs(tab1) do
print(k .. " - " .. v)
end
tab1.key1 = nil
for k, v in pairs(tab1) do
print(k .. " - " .. v)
end
output:
1 - val3
key2 - val2
key1 - val1
1 - val3
key2 - val2
- 注意:
nil
作比较时应该加上双引号:
> type(X)
nil
> type(X)==nil
false
> type(X)=="nil"
true
boolean
boolean
包含两个值:false
和true
- Lua 只把
false
和nil
看作false
,其余均为true
(0
也为true
)
number
- Lua 默认只有一种
number
类型 –double
(双精度)类型- 默认类型可以修改
luaconf.h
里的定义
- 默认类型可以修改
-- 均输出 number
print(type(2))
print(type(2.2))
print(type(7.8263692594256e-06))
string
string
由一对双引号或单引号来表示
> print("'Hello', \"World\"") -- 支持单引号里嵌套双引号,但嵌套单引号需要使用转义字符
'Hello', "World"
> print('\'Hello\', "World"') -- 支持双引号里嵌套单引号,但嵌套双引号需要使用转义字符
'Hello', "World"
- 也可以用 2 个方括号
[[ ]]
来表示"一块"字符串, 可以跨越多行且默认加上换行符,且里面可以随便加单引号或双引号
html = [[
<html>
<head></head>
<body>
<a href="http://www.runoob.com/">菜鸟教程</a>
</body>
</html>
]]
print(html)
字符串连接 ..
> print('Hello' .. ' World')
Hello World
string
和 number
的转换
显式类型转换:
tostring(num)
tonumber(s)
隐式类型转换
- 运行时,Lua 会自动在
string
和number
之间自动进行类型转换:- 在对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字
- 在对两个
number
进行连接运算时,会自动将其结果转为string
> print("2" + 6)
8.0
> print("2" + "6")
8.0
> print("2 + 6")
2 + 6
> print("-2e2" * "6")
-1200.0
> print("error" + 1)
stdin:1: attempt to perform arithmetic on a string value
stack traceback:
stdin:1: in main chunk
[C]: in ?
>
-- 当在一个数字后面写 `..` 时,必须加上空格以防止被解释错
> print(111 .. 222)
111222
> print(type(111 .. 222))
string
计算字符串长度 #
实际上输出的是字符串所占字节数。对于英文来说一个字母正好是一个字节,因此输出的正好是字符串长度;但是对中文就不一定了
> len = "www.runoob.com"
> print(#len)
14
> print(#"www.runoob.com")
14
> print(#'你好')
4
字符串操作
string.upper
, string.lower
string.upper(argument)
: 字符串全部转为大写字母string.lower(argument)
string.reverse
string.reverse(arg)
: 字符串反转
> string.reverse("Lua")
auL
string.char
, string.byte
string.char(arg)
,string.byte(arg[,int])
:char
将整型数字转成字符并连接,byte
转换字符为整数值(可以指定某个字符,默认第一个字符)
> string.char(97,98,99,100)
abcd
> string.byte("ABCD",4)
68
> string.byte("ABCD")
65
string.len
string.len(arg)
: 计算字符串长度
string.len("abc")
3
string.rep
string.rep(string, n)
: 返回字符串string
的n
个拷贝
> string.rep("abcd",2)
abcdabcd
string.format
string.format(...)
: 返回一个类似printf
的格式化字符串
> string.format("the value is:%d", 4)
the value is:4
- 转义码:
%c
- 接受一个数字, 并将其转化为ASCII码表中对应的字符%d
,%i
- 接受一个数字并将其转化为有符号的整数格式%o
- 接受一个数字并将其转化为八进制数格式%u
- 接受一个数字并将其转化为无符号整数格式%x
- 接受一个数字并将其转化为十六进制数格式, 使用小写字母%X
- 接受一个数字并将其转化为十六进制数格式, 使用大写字母%e
- 接受一个数字并将其转化为科学记数法格式, 使用小写字母e%E
- 接受一个数字并将其转化为科学记数法格式, 使用大写字母E%f
- 接受一个数字并将其转化为浮点数格式%g
(%G
) - 接受一个数字并将其转化为%e(%E, 对应%G)及%f中较短的一种格式%q
- 接受一个字符串并将其转化为可安全被Lua编译器读入的格式%s
- 接受一个字符串并按照给定的参数格式化该字符串
- 为进一步细化格式, 可以在
%
号后添加参数. 参数将以如下的顺序读入:- (1) 符号: 一个
+
号表示其后的数字转义符将让正数显示正号. 默认情况下只有负数显示符号. - (2) 占位符: 一个
0
, 在后面指定了字串宽度时占位用. 不填时的默认占位符是空格. - (3) 对齐标识: 在指定了字串宽度时, 默认为右对齐, 增加
-
号可以改为左对齐. - (4) 宽度数值
- (5) 小数位数/字串裁切: 在宽度数值后增加的小数部分
n
n
n, 若后接
f
(浮点数转义符, 如%6.3f
)则设定该浮点数的小数只保留 n n n 位, 若后接s
(字符串转义符, 如%5.3s
) 则设定该字符串有 5 位宽度,但只显示前 3 位.
- (1) 符号: 一个
string.sub
(按位置截取)
string.sub(s, i [, j])
: 截取字符串i
为截取开始位置。j
为截取结束位置,默认为-1
,即最后一个字符
string.sub('Hello', 2, 2) --> e
string.gsub
(按照匹配模式进行替换)
string.gsub(mainString,findString,replaceString,num)
: 在字符串中替换
mainString
为要操作的字符串,findString
为被替换的字符,replaceString
为要替换的字符,num
为替换次数(可以忽略,默认为全部替换)
> string.gsub("aaaa","a","z",3);
zzza 3
注意:替换是按照字节来替换的,如:
> print(string.gsub("hello,up-down!", "%A", ".")) -- 中文逗号,占两个字节
hello..up.down. 4
string.find
(按照匹配模式查找,返回位置)
string.find(str, substr, [init, [end]])
: 在一个指定的目标字符串中搜索指定的内容(第三个参数为索引),返回其具体位置。不存在则返回nil
> string.find("Hello Lua user", "Lua", 1)
7 9
string.gmatch
(按照匹配模式查找,返回迭代器)
string.gmatch(str, pattern)
: 返回一个迭代器函数,每一次调用这个函数,返回一个在字符串str
找到的下一个符合pattern
描述的子串。如果参数pattern
描述的字符串没有找到,迭代函数返回nil
> for word in string.gmatch("Hello Lua user", "%a+") do print(word) end
Hello
Lua
user
string.match
(按照匹配模式查找,返回所有捕获结果)
string.match(str, pattern, init)
:string.match()
只寻找源字串str
中的第一个配对. 参数init
可选, 指定搜寻过程的起点, 默认为 1
在成功配对时, 函数将返回配对表达式中的所有捕获结果; 如果没有设置捕获标记, 则返回整个配对字符串. 当没有成功的配对时, 返回nil
如果单个匹配模式不用小括号包含的话,最后会返回完整匹配的字符串:
> a, b = string.match("I have 2 questions for you.", "%d+ %a+")
> print(a, b)
2 questions nil
如果 pattern中 每个匹配模式用小括号包含的话,最后会按照多返回值的方式被捕获到:
> a, b = string.match("I have 2 questions for you.", "(%d+) (%a+)")
> print(a, b)
2 questions
> print(string.format("%d, %q", a, b))
2, "questions"
匹配模式
用于模式匹配函数 string.find
, string.gmatch
, string.gsub
, string.match
-
Lua 中的匹配模式直接用常规的字符串来描述
-
你还可以在模式串中使用字符类
字符类指可以匹配一个特定字符集合内任何字符的模式项。比如,字符类%d
匹配任意数字。所以你可以使用模式串%d%d/%d%d/%d%d%d%d
搜索dd/mm/yyyy
格式的日期:
下面的表列出了 Lua 支持的所有字符类:
-
单个字符(除 ^ $ ( ) % . [ ] * + - ? 外): 与该字符自身配对
-
.
(点): 与任何字符配对 -
%a
: 与任何字母配对 -
%c
: 与任何控制符配对(例如\n
) -
%d
: 与任何数字配对 -
%l
: 与任何小写字母配对 -
%p
: 与任何标点(punctuation)配对 -
%s
: 与空白字符配对 -
%u
: 与任何大写字母配对 -
%w
: 与任何字母/数字配对 -
%x
: 与任何十六进制数配对 -
%z
: 与任何代表0的字符配对 -
%x
(此处x
是非字母非数字字符): 与字符x
配对. 主要用来处理表达式中有功能的字符( ^ $ ( ) % . [ ] * + - ? )的配对问题, 例如%%
与%
配对 -
[数个字符类]
: 与任何[]
中包含的字符类配对. 例如[%w_]
与任何字母/数字, 或下划线符号(_)配对 -
[^数个字符类]
: 与任何不包含在[]
中的字符类配对. 例如[^%s]
与任何非空白字符配对 -
当上述的字符类用大写书写时, 表示与非此字符类的任何字符配对. 例如,
%S
表示与任何非空白字符配对
模式条目可以是:
- 单个字符类匹配该类别中任意单个字符;
- 单个字符类跟一个 ‘
*
’, 将匹配零或多个该类的字符。 这个条目总是匹配尽可能长的串; - 单个字符类跟一个 ‘
+
’, 将匹配一或更多个该类的字符。 这个条目总是匹配尽可能长的串; - 单个字符类跟一个 ‘
-
’, 将匹配零或更多个该类的字符。 和 ‘*
’ 不同, 这个条目总是匹配尽可能短的串; - 单个字符类跟一个 ‘
?
’, 将匹配零或一个该类的字符。 只要有可能,它会匹配一个; %n
, 这里的n
可以从 1 到 9; 这个条目匹配一个等于n
号捕获物的子串。%bxy
, 这里的x
和y
是两个明确的字符; 这个条目匹配以x
开始y
结束, 且其中x
和y
保持 平衡 的字符串。 意思是,如果从左到右读这个字符串,对每次读到一个x
就+1
,读到一个y
就-1
, 最终结束处的那个y
是第一个记数到 0 的y
举个例子,条目%b()
可以匹配到括号平衡的表达式%f[set]
, 指 边境模式; 这个条目会匹配到一个位于set
内某个字符之前的一个空串, 且这个位置的前一个字符不属于set
。 匹配出的那个空串的开始和结束点的计算就看成该处有个字符 ‘\0’ 一样
例如,
print(string.gsub('a1', '%f[%d]', 'b')) --> ab1 1
模式:
- 模式 指一个模式条目的序列。 在模式最前面加上符号 ‘
^
’ 将锚定从字符串的开始处做匹配。 在模式最后面加上符号 ‘$
’ 将使匹配过程锚定到字符串的结尾。 如果 ‘^
’ 和 ‘$
’ 出现在其它位置,它们均没有特殊含义,只表示自身
捕获:
-
模式可以在内部用小括号括起一个子模式; 这些子模式被称为 捕获物。 当匹配成功时,由 捕获物 匹配到的字符串中的子串被保存起来用于未来的用途。 捕获物以它们左括号的次序来编号
例如,对于模式 “(a*(.)%w(%s*))
” , 字符串中匹配到 “a*(.)%w(%s*)
” 的部分保存在第一个捕获物中 (因此是编号 1 ); 由 “.
” 匹配到的字符是 2 号捕获物, 匹配到 “%s*
” 的那部分是 3 号。 -
作为一个特例,空的捕获
()
将捕获到当前字符串的位置(它是一个数字)。
例如,如果将模式 “()aa()
” 作用到字符串 “flaaap
” 上,将产生两个捕获物: 3 和 5
table
(表)
table
的创建是通过构造表达式来完成,最简单构造表达式是{}
,用来创建一个空表。也可以在表里添加一些数据,直接初始化表:
-- 创建一个空的 table
local tbl1 = {}
-- 直接初始表
local tbl2 = {"apple", "pear", "orange", "grape"}
当我们为
table
a
设置元素,然后将a
赋值给b
,则a
与b
都指向同一个内存。如果a
设置为nil
,则b
同样能访问table
的元素。如果没有指定的变量指向a
,Lua 的垃圾回收机制会清理相对应的内存
table
其实是一个 关联数组,数组的索引可以是数字、字符串或表类型(但不能是nil
)
-- table_test.lua
a = {}
a["key"] = "value"
key = 10
a[key] = 22
a[key] = a[key] + 11
for k, v in pairs(a) do
print(k .. " : " .. v)
end
output:
key : value
10 : 33
- Lua 里表的默认初始索引一般以 1 开始
-- table_test2.lua
local tbl = {"apple", "pear", "orange", "grape"}
for key, val in pairs(tbl) do
print("Key", key)
end
output:
Key 1
Key 2
Key 3
Key 4
table
不会固定长度大小,有新数据添加时table
长度会自动增长,没初始化的table
都是nil
> a = {}
> print(a['none'])
nil
- 使用
pairs
遍历打印输出时,会先按照顺序输出表的值,然后再按照键值对的键的哈希值打印
tab = {"Hello", "World", a = 1, b = 2, z = 3, x = 10, y = 20, "Good", "Bye"}
for k,v in pairs(tab) do
print(k.." "..v)
end
output:
1 Hello
2 World
3 Good
4 Bye
x 10
y 20
z 3
b 2
a 1
索引
t[i]
t.i -- 当索引为字符串类型时的一种简化写法
> site = {}
> site["key"] = "www.runoob.com"
> print(site["key"])
www.runoob.com
> print(site.key)
www.runoob.com
数组
array = {"Lua", "Tutorial"}
for i = 1, 2 do
print(array[i])
end
- 数组索引默认为 1,但其实你可以用任何整数值做索引:
array = {}
for i= -2, 2 do
array[i] = i *2
end
for i = -2,2 do
print(array[i])
end
- 通过
table
也可以灵活地建立多维数组:
-- 初始化数组
array = {}
for i=1,3 do
array[i] = {}
for j=1,3 do
array[i][j] = i*j
end
end
-- 访问数组
for i=1,3 do
for j=1,3 do
print(array[i][j])
end
end
table
操作
table.concat
连接
table.concat(table [, sep [, start [, end]]]):
- 参数中指定
table
的数组部分从start
位置到end
位置的所有元素, 元素间以指定的分隔符(sep
)隔开
fruits = {"banana","orange","apple"}
-- 返回 table 连接后的字符串
print("连接后的字符串 ",table.concat(fruits))
-- 指定连接字符
print("连接后的字符串 ",table.concat(fruits,", "))
-- 指定索引来连接 table
print("连接后的字符串 ",table.concat(fruits,", ", 2,3))
output:
连接后的字符串 bananaorangeapple
连接后的字符串 banana, orange, apple
连接后的字符串 orange, apple
table.insert
插入和 table.remove
移除
table.insert(table, [pos,] value)
- 在
table
的数组部分指定位置(pos
)插入值为value
的一个元素.pos
参数可选, 默认为数组部分末尾
table.remove(table [, pos])
- 返回
table
数组部分位于pos
位置的元素. 其后的元素会被前移.pos
参数可选, 默认为table
长度, 即从最后一个元素开始删除
fruits = {"banana","orange","apple"}
-- 在末尾插入
table.insert(fruits,"mango")
print("索引为 4 的元素为 ",fruits[4])
-- 在索引为 2 的键处插入
table.insert(fruits,2,"grapes")
print("索引为 2 的元素为 ",fruits[2])
print("最后一个元素为 ",fruits[5])
table.remove(fruits)
print("移除后最后一个元素为 ",fruits[5])
output:
索引为 4 的元素为 mango
索引为 2 的元素为 grapes
最后一个元素为 mango
移除后最后一个元素为 nil
table.sort
排序
table.sort(table [, comp])
- 默认对给定的
table
进行升序排序
fruits = {"banana","orange","apple","grapes"}
print("排序前")
for k,v in ipairs(fruits) do
print(k,v)
end
table.sort(fruits)
print("排序后")
for k,v in ipairs(fruits) do
print(k,v)
end
output:
排序前
1 banana
2 orange
3 apple
4 grapes
排序后
1 apple
2 banana
3 grapes
4 orange
- 排序也支持自定义排序规则,当返回
true
时,a
排b
前面:
table.sort(t, function(a, b) return a.A > b.A; end) --降序
function
function
:由 C 或 Lua 编写的函数.function
也是对象,也可以存在变量里
-- function_test.lua
function factorial1(n)
if n == 0 then
return 1
else
return n * factorial1(n - 1)
end
end
print(factorial1(5)) --> 120
factorial2 = factorial1
print(factorial2(5)) --> 120
thread
(线程)
- 在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西
线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。
userdata
(自定义类型)
userdata
是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是struct
和 指针)存储到 Lua 变量中调用
迭代器
- 迭代器用来遍历标准模板库容器中的部分或全部元素
- 在 Lua 中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。Lua 的迭代器包含以下两种类型
- 无状态的迭代器
- 多状态的迭代器
pairs
& ipairs
pairs
可以遍历表中所有的key
,value
(包括nil
)ipairs
则只能从下标为1开始遍历所有数组下标的值,也就是说如果ipairs
在迭代过程中会直接跳过所有手动设定key
值的变量。并且在遇到nil
时直接停止
tab = {1,2,a= nil,"d", nil, "c"}
for i,v in ipairs(tab) do
print(i,v)
end
output:
1 1
2 2
3 d
泛型 for
迭代器
- 泛型
for
在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量 - 泛型
for
迭代器提供了集合的key
/value
对
for k, v in pairs(t) do -- k, v 为键值对
print(k, v)
end
下面我们看看泛型 for
的执行过程:
- 首先,初始化,计算
in
后面表达式的值,表达式应该返回泛型for
需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil
补足,多出部分会被忽略 - 将状态常量和控制变量作为参数调用迭代函数(注意:对于
for
结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数) - 将迭代函数返回的值赋给变量列表
- 如果返回的第一个值为
nil
循环结束,否则执行循环体 - 回到第二步再次调用迭代函数
无状态的迭代器
- 无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价
- 每一次迭代,迭代函数都是用两个变量(状态常量(被遍历的表) 和控制变量(当前的索引下标))的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素
- 这种无状态迭代器的典型的简单的例子是
ipairs
,它遍历数组的每一个元素
- 这种无状态迭代器的典型的简单的例子是
- 以下实例我们使用了一个简单的函数来实现迭代器,实现 数字
n
的平方:
function square(iteratorMaxCount,currentNumber)
if currentNumber < iteratorMaxCount then
currentNumber = currentNumber + 1
return currentNumber, currentNumber * currentNumber
end
end
for i,n in square,3,0
do
print(i,n)
end
output:
1 1
2 4
3 9
-- ipairs 的实现
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
内,所以迭代函数通常不需要第二个参数
array = {"Google", "Runoob"}
-- `elementIterator` 内使用了闭包函数,实现计算集合大小并输出各个元素
function elementIterator (collection)
local index = 0
local count = #collection
-- 闭包函数
return function ()
index = index + 1
if index <= count
then
-- 返回迭代器的当前元素
return collection[index]
end
end
end
for element in elementIterator(array)
do
print(element)
end
output:
Google
Runoob
循环
while
循环
while(condition)
do
statements
end
for
循环
for var=exp1,exp2,exp3 do
<执行体>
end
var
从exp1
变化到exp2
,每次变化以exp3
为步长递增var
,并执行一次 “执行体”。exp3
是可选的,默认为 1
for
的三个表达式在循环开始前一次性求值,以后不再进行求值
a = 3
function f(x)
return x * 2
end
for i = 1, f(a) do
print(i)
a = a - 1
end
output:
1
2
3
4
5
6
泛型for
循环
- 通过一个迭代器函数来遍历所有值
--打印数组a的所有值
a = {"one", "two", "three"}
for i, v in ipairs(a) do
print(i, v)
end
i
是数组索引值v
是对应索引的数组元素值ipairs
是一个迭代器函数,用来迭代数组
repeat...until
repeat
statements
until( condition )
break
语句
- 跳出最内层循环的执行
goto
语句
goto Label
Label 的格式为:
:: Label ::
local a = 1
::label:: print("--- goto label ---")
a = a + 1
if a < 3 then
goto label
end
output:
--- goto label ---
--- goto label ---
可以在 lable 中设置多个语句:
i = 0
::s1:: do
print(i)
i = i + 1
end
if i > 3 then
os.exit() -- i 大于 3 时退出
end
goto s1
output:
0
1
2
3
控制结构语句
注意,只有
false
和nil
才被归为false
,其他值包括 0 均为true
if
语句
if(布尔表达式)
then
-- statement
end
if...else
语句
if(布尔表达式)
then
-- statement
else
-- statement
end
if...elseif...else
语句
if( 布尔表达式 1)
then
-- statement
elseif( 布尔表达式 2)
then
-- statement
else
-- statement
end
函数
optional_function_scope function function_name( argument1, argument2..., argumentn)
function_body
return result_params_comma_separated
end
optional_function_scope
: 该参数是可选的,用于指定函数是全局函数还是局部函数,默认为全局函数,如果你需要设置函数为局部函数需要使用关键字local
result_params_comma_separated
: 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开
可以直接在定义时将函数赋值给变量:
myprint = function(param)
-- statement
end
匿名函数
-- function_test2.lua
function testFun(tab,fun)
for k ,v in pairs(tab) do
print(fun(k,v));
end
end
tab={key1="val1",key2="val2"};
testFun(tab,
function(key,val)--匿名函数
return key.."="..val;
end
);
output:
key1 = val1
key2 = val2
可变参数
固定参数必须放在变长参数之前
- 函数参数列表中使用三点
...
表示函数有可变的参数
function fwrite(fmt, ...) ---> 固定的参数fmt
return io.write(string.format(fmt, ...))
end
fwrite("runoob\n") --->fmt = "runoob", 没有变长参数。
fwrite("%d%d\n", 1, 2) --->fmt = "%d%d", 变长参数为 1 和 2
function add(...)
local s = 0
for i, v in ipairs{...} do --> {...} 表示一个由所有变长参数构成的数组
s = s + v
end
return s
end
print(add(3,4,5,6,7)) --->25
我们可以将可变参数赋值给一个变量:
function average(...)
result = 0
local arg = {...} --> arg 为一个表
for i, v in ipairs(arg) do
result = result + v
end
print("总共传入 " .. #arg .. " 个数")
return result / #arg
end
print("平均值为", average(10,5,3,4,5,6))
我们也可以通过 select
函数来访问变长参数
select('#', …)
返回可变参数的长度select(n, …)
返回n
到select('#',…)
的参数 ( − 1 -1 −1 表示最后一个参数的位置)
do
function foo(...)
for i = 1, select('#', ...) do -->获取参数总数
local arg = select(i, ...); -->读取参数 select 返回多个值,但 arg只接受第一个
print("arg", arg);
end
end
foo(1, 2, 3, 4);
end
output:
arg 1
arg 2
arg 3
arg 4
注意一下多返回值的函数在赋值时的情况,仅仅只有放在所有逗号之后的那个函数会把返回值展开:
function add()
return 1,0
end
local b,c,d,e = add(),add()
print(b) -- 1
print(c) -- 1
print(d) -- 0
print(e) -- nil
运算符
算术运算符
+
, -
, *
, /
, %
(取余), ^
(乘幂)
注意:
^
是右结合的,即:x^y^z <--> x^(y^z)
关系运算符
==
, ~=
(不等于), >
, <
, >=
, <=
逻辑运算符
and
, or
, not
其他运算符
- Lua 中没有三元表达式,但是可以利用
and
和or
来实现三元表达式:
(condition and {result1} or {result2})[1]
参考:https://www.runoob.com/w3cnote/trinocular-operators-in-lua.html
..
: 连接两个字符串
#
: 一元运算符,返回字符串或表的长度
这里返回的表长实际上运作的跟直观感觉不一样… 只能对照着源码实现来看
可参考:https://www.runoob.com/w3cnote/lua-table-length-analysis.html
运算符优先级
优先级从高到低 |
---|
^ |
not , - (unary) (负号) |
* , / , % |
+ , - |
.. (字符串连接运算) |
< , > , <= , >= , ~= , == |
and |
or |
Lua 模块与包
- Lua 的模块是由变量、函数等已知元素组成的
table
,因此创建一个模块就是创建一个table
,然后把需要导出的常量、函数放入其中,最后返回这个table
就行
-- 文件名为 module.lua
-- 定义一个名为 module 的模块
module = {}
-- 定义一个常量
module.constant = "这是一个常量"
-- 定义一个函数
function module.func1()
io.write("这是一个公有函数!\n")
end
-- 私有函数不能从外部直接访问,必须通过公有函数来调用
local function func2()
print("这是一个私有函数!")
end
function module.func3()
func2()
end
return module
require
函数
require
函数用于加载模块。例如:
require("<模块名>")
require "<模块名>"
- 执行
require
后会返回一个由模块常量或函数组成的table
,并且还会定义一个包含该table
的全局变量
-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")
print(module.constant)
module.func3()
- 可以给加载的模块定义一个别名变量,方便调用:
-- test_module2.lua 文件
-- module 模块为上文提到到 module.lua
-- 别名变量 m
local m = require("module")
print(m.constant)
m.func3()
output:
这是一个常量
这是一个私有函数!
加载机制
- 对于自定义的模块,模块文件不是放在哪个文件目录都行,函数
require
有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块 require
用于搜索 Lua 文件的路径是存放在全局变量package.path
中,当 Lua 启动后,会以环境变量LUA_PATH
的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化
- 当然,也可以自定义设置环境变量
LUA_PATH
(Linux),在当前用户根目录下打开.profile
文件(没有则创建,打开.bashrc
文件也可以)。文件路径以 “;
” 号分隔,最后的 2 个 “;;
” 表示新加的路径后面加上原来的默认路径:
#LUA_PATH
export LUA_PATH="~/lua/?.lua;;"
- 接着,更新环境变量参数,使之立即生效
source ~/.profile
- 如果找过目标文件,则会调用
package.loadfile
来加载模块。否则,就会去找 C 程序库。搜索的文件路径是从全局变量package.cpath
获取,而这个变量则是通过环境变量LUA_CPATH
来初始化的。搜索的策略跟上面的一样,只不过现在换成搜索的是so
或dll
类型的文件。如果找得到,那么require
就会通过package.loadlib
来加载它
C 包
- Lua 和 C 是很容易结合的,可以使用 C 为 Lua 写包。与 Lua 中写包不同,C 包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制
- Lua 在一个叫
loadlib
的函数内提供了所有的动态连接的功能。这个函数有两个参数: 库的绝对路径和初始化函数:
local path = "/usr/local/lua/lib/libluasocket.so"
local f = loadlib(path, "luaopen_socket")
loadlib
函数加载指定的库并且连接到 Lua,然而它并不打开库(也就是说没有调用初始化函数),反之他返回初始化函数作为 Lua 的一个函数,这样我们就可以直接在Lua中调用他
如果加载动态库或者查找初始化函数时出错,loadlib
将返回 nil
和错误信息。我们可以修改前面一段代码,使其检测错误然后调用初始化函数:
local path = "/usr/local/lua/lib/libluasocket.so"
-- 或者 path = "C:\\windows\\luasocket.dll",这是 Window 平台下
local f = assert(loadlib(path, "luaopen_socket"))
f() -- 真正打开库
一般情况下我们期望二进制的发布库包含一个与前面代码段相似的 stub 文件,安装二进制库的时候可以随便放在某个目录,只需要修改 stub 文件对应二进制库的实际路径即可。
将 stub 文件所在的目录加入到 LUA_PATH
,这样设定后就可以使用 require
函数加载 C 库了
Lua 元表 (Metatable)
- 在 Lua
table
中我们可以访问对应的 key 来得到 value 值,但是却无法对两个table
进行操作。因此 Lua 提供了元表 (Metatable),允许我们改变table
的行为,每个行为关联了对应的元方法- 例如,使用元表我们可以定义 Lua 如何计算两个
table
的相加操作a + b
. 当 Lua 试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫 “__add
” 的字段,若找到,则调用对应的值。"__add
“等即时字段,其对应的值(往往是一个函数或是table
)就是"元方法”
- 例如,使用元表我们可以定义 Lua 如何计算两个
- 有两个很重要的函数来处理元表:
setmetatable(table,metatable)
: 对指定table
设置元表,如果元表中存在__metatable
键值,setmetatable
会失败getmetatable(table)
: 返回对象的元表
mytable = {} -- 普通表
mymetatable = {} -- 元表
setmetatable(mytable,mymetatable) -- 把 mymetatable 设为 mytable 的元表
-- 等价于
mytable = setmetatable({},{})
- 以下为返回对象元表:
getmetatable(mytable) -- 这回返回mymetatable
__index
元方法
- 这是 metatable 最常用的键
- 当你通过键来访问
table
的时候,如果这个键没有值,那么 Lua 就会寻找该table
的 metatable(假定有 metatable)中的__index
键
- 如果
__index
包含一个表格,Lua 会在表格中查找相应的键
$ lua
Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio
> other = { foo = 3 }
> t = setmetatable({}, { __index = other })
> t.foo
3
> t.bar
nil
- 如果
__index
包含一个函数的话,Lua 就会调用那个函数,table
和键会作为参数传递给函数
mytable = setmetatable({key1 = "value1"}, {
__index = function(mytable, key)
if key == "key2" then
return "metatablevalue"
else
return nil
end
end
})
-- 等价于
mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
print(mytable.key1, mytable.key2) --> value1 metatablevalue
__newindex
元方法
- 当你给表的一个缺少的索引赋值,解释器就会查找
__newindex
元方法:如果存在则调用这个函数而不进行赋值操作
mymetatable = {}
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })
mytable.newkey = "新值2"
print(mytable.newkey, mymetatable.newkey) --> nil 新值2
mytable.key1 = "新值1"
print(mytable.key1, mymetatable.key1) --> 新值1 nil
-- 使用 rawset 函数来更新表
mytable = setmetatable({}, {
__newindex = function(mytable, key, value)
rawset(mytable, key, "\""..value.."\"")
end
})
mytable.key1 = 4
print(mytable.key1) --> "4"
为表添加操作符
- 表中对应的操作列表如下:
元方法 | 对应的运算符 |
---|---|
__add | + |
__sub | - |
__mul | * |
__div | / |
__mod | % |
__unm | - |
__concat | … |
__eq | == |
__lt | < |
__le | <= |
-- 计算表中最大值,即计算表的元素个数
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
-- 两表相加操作
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
})
secondtable = {4,5,6}
mytable = mytable + secondtable
for k,v in ipairs(mytable) do
print(k,v)
end
output:
1 1
2 2
3 3
4 4
5 5
6 6
__call
元方法
__call
元方法在 Lua 调用一个值时调用
-- 计算表中最大值,即计算表的元素个数
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
-- 定义元方法__call
mytable = setmetatable({10}, {
__call = function(mytable, newtable) -- 计算表中元素的和
sum = 0
for i = 1, table_maxn(mytable) do
sum = sum + mytable[i]
end
for i = 1, table_maxn(newtable) do
sum = sum + newtable[i]
end
return sum
end
})
newtable = {10,20,30}
print(mytable(newtable)) --> 70
__tostring
元方法
__tostring
元方法用于修改表的输出行为
mytable = setmetatable({ 10, 20, 30 }, {
__tostring = function(mytable)
sum = 0
for k, v in pairs(mytable) do
sum = sum + v
end
return "表所有元素的和为 " .. sum
end
})
print(mytable) --> 表所有元素的和为 60
Lua 面向对象
- 对象由属性和方法组成。LUA 中最基本的结构是
table
,所以需要用table
来描述对象的属性.function
可以用来表示方法。那么 LUA 中的类可以通过table
+function
模拟出来 - 至于继承,可以通过
metetable
模拟出来
A class is a table which has a particular metatable.
Account = {balance = 0}
function Account.withdraw(v)
Account.balance = Account.balance - v
end
Account.withdraw(100.00)
-- 元类
Rectangle = {area = 0, length = 0, breadth = 0}
-- 派生类的方法 new
-- 冒号的作用就是:定义函数时,给函数的添加隐藏的第一个参数self;调用函数时,默认把当前调用者作为第一个参数传递进去
function Rectangle:new (o, length, breadth)
o = o or {}
setmetatable(o, self) -- 把 Rectangle 设为 o 的元表
self.__index = self
self.length = length or 0
self.breadth = breadth or 0
self.area = length * breadth;
return o
end
-- 派生类的方法 printArea
function Rectangle:printArea()
print("矩形面积为 ", self.area)
end
创建对象
r = Rectangle:new(nil, 10, 20) -- Rectangle.new(self, nil, 10, 20)
访问属性
- 我们可以使用点号(
.
)来访问类的属性:
print(r.length)
访问成员函数
- 我们可以使用冒号
:
来访问类的成员函数:
r:printArea()
-- 元类
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)
o = o or {}
setmetatable(o, self)
self.__index = self
side = side or 0
self.area = side*side;
return o
end
-- 基础类方法 printArea
function Shape:printArea ()
print("面积为 ",self.area)
end
-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea() --> 面积为 100
Lua 继承
-- Meta class
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)
o = o or {}
setmetatable(o, self)
self.__index = self
side = side or 0
self.area = side*side;
return o
end
-- 基础类方法 printArea
function Shape:printArea ()
print("面积为 ",self.area)
end
-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea()
Square = Shape:new()
-- 派生类方法 new
function Square:new (o,side)
o = o or Shape:new(o,side)
setmetatable(o, self)
self.__index = self
return o
end
-- 派生类方法 printArea
function Square:printArea ()
print("正方形面积为 ",self.area)
end
-- 创建对象
mysquare = Square:new(nil,10)
mysquare:printArea()
Rectangle = Shape:new()
-- 派生类方法 new
function Rectangle:new (o,length,breadth)
o = o or Shape:new(o)
setmetatable(o, self)
self.__index = self
self.area = length * breadth
return o
end
-- 派生类方法 printArea
function Rectangle:printArea ()
print("矩形面积为 ",self.area)
end
-- 创建对象
myrectangle = Rectangle:new(nil,10,20)
myrectangle:printArea()
output:
面积为 100
正方形面积为 100
矩形面积为 200