初识Lua
概念:Lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
特性:
轻量级: 它用标准C语言编写并以源代码形式开放,编译后仅仅一百余K,可以很方便的嵌入别的程序里。
可扩展: Lua提供了非常易于使用的扩展接口和机制:由宿主语言(通常是C或C++)提供这些功能,Lua可以使用它们,就像是内置的功能一样。
支持面向过程(procedure-oriented)编程和函数式编程(functional programming);
自动内存管理;
只提供了一种通用类型的表(table),用它可以实现数组,哈希表,集合,对象;
语言内置模式匹配;
闭包(closure);
函数也可以看做一个值;
提供多线程(协同程序,并非操作系统所支持的线程)支持;
通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制,比如数据抽象,虚函数,继承和重载等。
应用场景:
- 游戏开发
- 独立应用脚本
- Web应用脚本
- 扩展和数据库插件如:MySQL Proxy 和 MySQL WorkBench
- 安全系统(如入侵检测系统)
安装Lua(Window系统)
Window下可以使用一个叫"SciTE"的IDE环境来执行lua程序,下载地址为:
Github下载地址:https://github.com/rjpcomputing/luaforwindows/releases
选第一个(LuaForWindow_v5.1.5-52.exe)
Google Code下载地址 : https://code.google.com/p/luaforwindows/downloads/list
双击安装后即可在该环境下编写Lua程序并运行。
Lua基础语法
注意:可以在命令行窗口中使用lua -i来启用交互式编程(这个命令其实就是启动了上图中蓝色图标的lua应用程序)。
交互式编程
脚本式编程
先创建一个以lua结尾的文件(如LuaStudy.lua文件),用SciTE打开该文件,再点击"RunPrograme"即可运行。
我们还可以cmd到该lua文件的位置,然后执行如下命令:
单行注释:--
多行注释:--[[注释内容--]]
标识符:标示符以一个字母A到Z或a到z或下划线 _ 开头后加上0个或多个字母,下划线,数字(0到9)。
注意:
最好不要使用下划线加大写字母的标示符,因为Lua的保留字也是这样的。
Lua不允许使用特殊字符如 @, $, 和 % 来定义标示符。Lua是一个区分大小写的编程语言。
关键字:以下列出了Lua的保留关键词。保留关键字不能作为常量或变量或其他用户自定义标示符。
and | break | do | else |
elseif | end | false | for |
function | if | in | local |
nil | not | or | repeat |
return | then | true | until |
while | goto |
注意:一般约定,以下划线开头连接一串大写字母的名字(比如 _VERSION)被保留用于Lua内部全局变量。
全局变量:
在默认情况下,变量总是认为是全局的。
全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil
print(value)
value = "赋值"
print(value)
value = nil --删除value
print(value)
--[[
输出结果:
nil
赋值
nil
--]]
如果你想删除一个全局变量,只需要将变量赋值为nil。
这样变量value就好像从没被使用过一样。换句话说, 当且仅当一个变量不等于nil时,这个变量即存在。
Lua数据类型
Lua是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。
Lua中有8个基本类型分别为:nil、boolean、number、string、userdata、function、thread和table。
数据类型 | 描述 |
---|---|
nil | 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。 |
boolean | 包含两个值:false和true。 |
number | 表示双精度类型的实浮点数 |
string | 字符串由一对双引号或单引号来表示 |
function | 由C或Lua编写的函数 |
userdata | 表示任意存储在变量中的C数据结构 |
thread | 表示执行的独立线路,用于执行协同程序 |
table | Lua中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在Lua里,table的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。 |
可以使用type函数测试给定变量或者值的类型:
print(type(nil))
print(type(true))
print(type(1000))
print(type("中国"))
print(type(type))
print(type({"A","B"}))
--[[
输出结果:
nil
boolean
number
string
function
table
--]]
nil(空)
nil类型表示一种没有任何有效值,它只有一个值nil,例如打印一个没有赋值的变量,便会输出一个 nil 值。
对于全局变量和table,nil还有一个"删除"作用,给全局变量或者table表里的变量赋一个nil值,等同于把它们删掉。
boolean(布尔值)
boolean类型只有两个可选值:true(真)和false(假),Lua把false和nil看作是false,其他的都为 true,数字0也是true。
number(数字)
Lua默认只有一种number类型 -- double(双精度)类型(默认类型可以修改luaconf.h里的定义)。
string(字符串)
字符串由一对双引号或单引号来表示。也可以用2个方括号"[[内容]]"来表示"一块"字符串。
在对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字:
print("3" + 7)
print("3" + "7")
print("3 + 7")
print("A" + 10)
--[[
输出结果:
10
10
3 + 7
lua: LuaStudy.lua:30: attempt to perform arithmetic on a string value
stack traceback:
LuaStudy.lua:30: in main chunk
[C]: ?
--]]
字符串连接使用:..
获取字符串长度:#、string.len、utf8.len
str01 = "AB"
str02 = "我是中国人"
print(str01..str02)
print(#str01) --输出:2
print(#str02) --输出:10
print(string.len(str01)) --输出:2
print(string.len(str02)) --输出:10
print(utf8.len(str01)) --输出:2
print(utf8.len(str02)) --输出:5
function(函数)
在Lua中,函数是被看作是"第一类值(First-Class Value)",函数可以存在变量里:
function myFun(value)
if value == 0 then
return 1
else
return value*myFun(value-1)
end
end
print(myFun(3)) --输出:6
local tempFun = myFun
print(tempFun(5)) --输出:120
function可以以匿名函数的方式通过参数传递:
function myFun(tab,fun)
for k,v in pairs(tab) do
print(fun(k,v))
end
end
tab = {"A","B","C","D"}
myFun(tab,function(k,v)
return k.." = "..v
end
)
--[[
输出结果:
1 = A
2 = B
3 = C
4 = D
--]]
table(表)
在Lua里,table的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。也可以在表里添加一些数据,直接初始化表:
--创建一个空的table
local tab1 = {}
--直接初始表
local tab2 = {"apple", "pear", "orange", "grape"}
Lua中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。
tab = {}
key = 10
tab["key"] = "value"
tab[key] = 50
tab[key] = tab[key]*2
for k,v in pairs(tab) do
print(k.." = "..v)
end
print(tab.key) --键为字符串的话可以这样访问
--[[
输出结果:
key = value
10 = 100
value
--]]
注意:
不同于其他语言的数组把0作为数组的初始索引,在Lua里表的默认初始索引一般以1开始。
table长度大小是不固定的,有新数据添加时table长度会自动增长,没初始的table都是nil。
thread(线程)
在Lua里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。
userdata(自定义类型)
userdata是一种用户自定义数据,用于表示一种由应用程序或C/C++语言库所创建的类型,可以将任意C/C++的任意数据类型的数据(通常是struct和指针)存储到Lua变量中调用。
Lua变量
变量在使用前,需要在代码中进行声明,即创建该变量。
编译程序执行代码之前编译器需要知道如何给语句变量开辟存储区,用于存储变量的值。
Lua变量有三种类型:全局变量、局部变量、表中的域。
Lua中的变量全是全局变量,哪怕是语句块或是函数里,除非用local显式声明为局部变量。
局部变量的作用域为从声明位置开始到所在语句块结束。
变量的默认值均为nil。
--StudyLua.lua脚本
a = 5 --全局变量
local b = 5 --局部变量
function MyFun()
c = 5 --全局变量
local d = 6 --局部变量
end
MyFun()
print(c,d) --输出:5和nil
do
local a = 6 --局部变量
b = 6 --重新赋值局部变量
print(a,b) --输出:6和6
end
print(a,b) --输出:5和6
赋值语句
赋值是改变一个变量的值和改变表域的最基本的方法。
s = "hello" .. "world"
tab.n = tab.n + 1
Lua可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。
a, b = 10, 2*x <--> a=10; b=2*x
遇到赋值语句Lua会先计算右边所有的值然后再执行赋值操作,所以我们可以这样进行交换变量的值:
x, y = y, x -- swap 'x' for 'y'
a[i], a[j] = a[j], a[i] -- swap 'a[i]' for 'a[j]'
当变量个数和值的个数不一致时,Lua会一直以变量个数为基础采取以下策略:
a. 变量个数 > 值的个数 按变量个数补足nil
b. 变量个数 < 值的个数 多余的值会被忽略
a, b, c = 0, 1
print(a,b,c) --> 0 1 nil
a, b = a+1, b+1, b+2 -- b+2会被忽略掉
print(a,b) --> 1 2
a, b, c = 0
print(a,b,c) --> 0 nil nil
上面最后一个例子是一个常见的错误情况,注意:如果要对多个变量赋值必须依次对每个变量赋值。
a, b, c = 0, 0, 0
print(a,b,c) --> 0 0 0
多值赋值经常用来交换变量,或将函数调用返回给变量:
a,b = fun() --fun()返回两个值,第一个赋给a,第二个赋给b。
应该尽可能的使用局部变量,有两个好处:
- 1. 避免命名冲突。
- 2. 访问局部变量的速度比全局变量更快。
索引
对table的索引使用方括号 []。Lua也提供了.操作。
t[i]
t.i -- 当索引为字符串类型时的一种简化写法
gettable_event(t,i) -- 采用索引访问本质上是一个类似这样的函数调用
site = {}
site["key"] = "www.runoob.com"
print(site["key"]) --输出:www.runoob.com
print(site.key) --输出:www.runoob.com
Lua循环
一组被重复执行的语句称之为循环体,能否继续重复,决定循环的终止条件。
循环结构是在一定条件下反复执行某段程序的流程结构,被反复执行的程序被称为循环体。
循环语句是由循环体及循环的终止条件两部分组成的。
Lua语言提供了以下几种循环处理方式:
循环类型 | 描述 |
---|---|
while 循环 | 在条件为true时,让程序重复地执行某些语句。执行语句前会先检查条件是否为true。 |
for 循环 | 重复执行指定语句,重复次数可在for语句中控制。 |
repeat...until | 重复执行循环,直到 指定的条件为真时为止 |
循环嵌套 | 可以在循环内嵌套一个或多个循环语句(while do ... end;for ... do ... end;repeat ... until;) |
Lua while循环
Lua编程语言中while循环语句在判断条件为true时会重复执行循环体语句。
语法
Lua编程语言中while循环语法:
while(condition) do
statements
end
statements(循环体语句)可以是一条或多条语句,condition(条件)可以是任意表达式,在 condition(条件)为true时执行循环体语句。
流程图如下:
在以上流程图中我们可以看出在condition(条件)为 false 时会跳过当前循环并开始脚本执行紧接着的语句。
实例
value = 0
while(value < 5) do
print(value)
value = value + 1
end
--[[
输出结果:
0
1
2
3
4
--]]
Lua for循环
Lua编程语言中for循环语句可以重复执行指定语句,重复次数可在for语句中控制。
Lua编程语言中for语句有两大类:
- 数值for循环
- 泛型for循环
数值for循环
Lua编程语言中数值for循环语法格式:
for i = exp1,exp2,exp3 do
--执行体
end
i从exp1变化到exp2,每次变化以exp3为步长递增i,并执行一次 "执行体"。exp3是可选的,如果不指定,默认为1。
for i=1,f(x) do
print(i)
end
for i=10,1,-1 do
print(i)
end
for的三个表达式在循环开始前一次性求值,以后不再进行求值。比如上面的f(x)只会在循环开始前执行一次,其结果用在后面的循环中。
验证如下:
function fun(x)
print("fun")
return x*2
end
for i=1,fun(2) do
print(i)
end
--[[
输出结果:
fun
1
2
3
4
--]]
可以看到函数f(x)只在循环开始前执行一次。
泛型for循环
泛型for循环通过一个迭代器函数来遍历所有值,类似java中的foreach语句。
Lua编程语言中泛型for循环语法格式:
tab = {"A","B","C"}
for k,v in ipairs(tab) do
print(k.." = "..v)
end
--[[
输出结果:
1 = A
2 = B
3 = C
--]]
拓展:pairs和ipairs的异同。
相同点:
- 两者都能遍历集合。
- 两者都会优先输出没有键(键为整数)的值。
不同点:
1.pairs
- 集合中整数和非整数的键都会遍历。
- 如果遇到nil,它会忽略该键值对(就像不存在一样),然后继续遍历集合。
2.ipairs
- 从集合的第1项开始,逐个输出其键值对。
- 如果遇到非整数的键,它会忽略该键值对(就像不存在一样),然后继续遍历集合。
- 如果遇到nil,它将终止遍历。
Lua repeat...until循环
Lua编程语言中repeat...until循环语句不同于for和while循环,for和while循环的条件语句在当前循环执行开始时判断,而repeat...until循环的条件语句在当前循环结束后判断。
语法
Lua编程语言中repeat...until循环语法格式:
repeat
statements
until(condition)
我们注意到循环条件判断语句(condition)在循环体末尾部分,所以在条件进行判断前循环体都会执行一次。
如果条件判断语句(condition)为 false,循环会重新开始执行,直到条件判断语句(condition)为true才会停止执行。
Lua repeat...until循环流程图如下:
实例
value = 0
repeat
print("value = "..value)
value = value + 1
until(value > 5)
--[[
输出结果:
value = 0
value = 1
value = 2
value = 3
value = 4
value = 5
--]]
Lua循环嵌套
Lua编程语言中允许循环中嵌入循环。以下实例演示了Lua循环嵌套的应用。
语法
Lua编程语言中for循环嵌套语法格式:
for init,max/min value, increment do
for init,max/min value, increment do
statements
end
statements
end
Lua编程语言中while循环嵌套语法格式:
while(condition) do
while(condition) do
statements
end
statements
end
Lua编程语言中repeat...until循环嵌套语法格式:
repeat
statements
repeat
statements
until(condition)
until(condition)
除了以上同类型循环嵌套外,我们还可以使用不同的循环类型来嵌套,如for循环体中嵌套while循环。
实例
以下实例使用了for循环嵌套:
j =2
for i=2,10 do
for j=2,(i/j) , 2 do
if(not(i%j))
then
break
end
if(j > (i/j))then
print("i 的值为:",i)
end
end
end
--[[
输出结果:
i 的值为:8
i 的值为:9
i 的值为:10
--]]
循环控制语句
循环控制语句用于控制程序的流程, 以实现程序的各种结构方式。
Lua支持以下循环控制语句:
控制语句 | 描述 |
---|---|
break 语句 | 退出当前循环或语句,并开始脚本执行紧接着的语句。 |
goto 语句 | 将程序的控制点转移到一个标签处。 |
Lua break语句
Lua编程语言break语句插入在循环体中,用于退出当前循环或语句,并开始脚本执行紧接着的语句。
如果你使用循环嵌套,break语句将停止最内层循环的执行,并开始执行的外层的循环语句。
语法
Lua编程语言中break语句语法格式:break
流程图:
实例
以下实例执行while循环,在变量a小于20时输出a的值,并在a大于15时终止执行循环:
a = 10
while( a < 20 ) do
print("a 的值为: "..a)
a = a + 1
if(a > 15) then
break --使用 break 语句终止循环
end
end
--[[
输出结果:
a 的值为: 10
a 的值为: 11
a 的值为: 12
a 的值为: 13
a 的值为: 14
a 的值为: 15
--]]
Lua goto语句
Lua语言中的goto语句允许将控制流程无条件地转到被标记的语句处。
语法
语法格式如下所示:goto Label
Label的格式为::: Label ::
实例
以下实例在判断语句中使用 goto:
local a = 1
::label:: print("--- goto label ---")
a = a+1
if a < 3 then
goto label -- a 小于 3 的时候跳转到标签 label
end
--[[
输出结果:
--- goto label ---
--- 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
--[[
输出结果:
0
1
2
3
--]]
使用goto语句,我们可以实现continue的功能:
for i=1, 3 do
if i <= 2 then
print(i, "yes continue")
goto continue
end
print(i, " no continue")
::continue::
print([[i'm end]])
end
--[[
输出结果:
1 yes continue
i'm end
2 yes continue
i'm end
3 no continue
i'm end
--]]
无限循环
在循环体中如果条件永远为true循环语句就会永远执行下去,以下以while循环为例:
while(true) do
print("循环将永远执行下去")
end
Lua流程控制
Lua编程语言流程控制语句通过程序设定一个或多个条件语句来设定。在条件为true时执行指定程序代码,在条件为false时执行其他指定代码。
以下是典型的流程控制流程图:
控制结构的条件表达式结果可以是任何值,Lua认为false和nil为假,true和非nil为真。
注意:在Lua中0为true;
Lua提供了以下控制结构语句:
语句 | 描述 |
---|---|
if 语句 | if语句由一个布尔表达式作为条件判断,其后紧跟其他语句组成。 |
if...else 语句 | if语句可以与else语句搭配使用, 在if条件表达式为false时执行else语句代码。 |
if 嵌套语句 | 你可以在if或else if中使用一个或多个if或else if语句。 |
Lua if语句
Lua if语句由一个布尔表达式作为条件判断,其后紧跟其他语句组成。
Lua if语句语法格式如下:
if(布尔表达式) then
--[ 在布尔表达式为 true 时执行的语句 --]
end
在布尔表达式为true时,if中的代码块会被执行,在布尔表达式为false时,紧跟在if语句end之后的代码会被执行。
Lua认为false和nil为假,true和非nil为真。要注意的是Lua中0为true。
if 语句流程图如下:
实例:以下实例用于判断变量a的值是否小于20。
value = 10
if(value < 20) then
print("value 小于 20")
end
print("value = "..value)
--[[
输出结果:
value 小于 20
value = 10
--]]
Lua if...else语句
if...else语句
Lua if语句可以与else语句搭配使用, 在if条件表达式为false时执行else语句代码块。
Lua if...else语句语法格式如下:
if(布尔表达式) then
--[ 布尔表达式为 true 时执行该语句块 --]
else
--[ 布尔表达式为 false 时执行该语句块 --]
end
在布尔表达式为true时,if中的代码块会被执行,在布尔表达式为false时,else的代码块会被执行。
Lua认为false和nil为假,true和非nil为真。要注意的是Lua中0为true。
if语句流程图如下:
实例:以下实例用于判断变量a的值。
--[ 定义变量 --]
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语句
Lua if语句可以与elseif...else语句搭配使用, 在if条件表达式为false时执行elseif...else语句代码块,用于检测多个条件语句。
Lua if...elseif...else语句语法格式如下:
if( 布尔表达式 1) then
--[ 在布尔表达式 1 为 true 时执行该语句块 --]
elseif( 布尔表达式 2) then
--[ 在布尔表达式 2 为 true 时执行该语句块 --]
elseif( 布尔表达式 3) then
--[ 在布尔表达式 3 为 true 时执行该语句块 --]
else
--[ 如果以上布尔表达式都不为 true 则执行该语句块 --]
end
实例:以下实例对变量a的值进行判断。
a = 100
if( a == 10 ) then
print("a 的值为 10")
elseif( a == 20 ) then
print("a 的值为 20")
elseif( a == 30 ) then
print("a 的值为 30")
else
print("没有匹配 a 的值")
end
print("a 的真实值为: "..a)
--[[
输出结果:
没有匹配 a 的值
a 的真实值为: 100
--]]
Lua if嵌套语句
Lua if语句允许嵌套, 这就意味着你可以在一个if或else if语句中插入其他的if或else if语句。
Lua if嵌套语句语法格式如下:
if( 布尔表达式 1) then
--布尔表达式 1 为 true 时执行该语句块
if(布尔表达式 2) then
--布尔表达式 2 为 true 时执行该语句块
end
end
你可以用同样的方式嵌套else if...else语句。
实例:以下实例用于判断变量a和b的值。
a = 100;
b = 200;
if( a == 100 ) then
if( b == 200 ) then
print("a 的值为 100 b 的值为 200");
end
end
print("a 的值为 :"..a);
print("b 的值为 :"..b);
--[[
输出结果:
a 的值为 100 b 的值为 200
a 的值为 :100
b 的值为 :200
--]]
Lua函数
在Lua中,函数是对语句和表达式进行抽象的主要方法。既可以用来处理一些特殊的工作,也可以用来计算一些值。
Lua提供了许多的内建函数,你可以很方便的在程序中调用它们,如print()函数可以将传入的参数打印在控制台上。
Lua函数主要有两种用途:
- 1.完成指定的任务,这种情况下函数作为调用语句使用;
- 2.计算并返回值,这种情况下函数作为赋值语句的表达式使用。
函数定义
Lua编程语言函数定义格式如下:
optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
function_body
return result_params_comma_separated
end
解析:
-
optional_function_scope: 该参数是可选的指定函数是全局函数还是局部函数,未设置该参数默认为全局函数,如果你需要设置函数为局部函数需要使用关键字local。
-
function_name: 指定函数名称。
-
argument1, argument2, argument3..., argumentn: 函数参数,多个参数以逗号隔开,函数也可以不带参数。
-
function_body: 函数体,函数中需要执行的代码语句块。
-
result_params_comma_separated: 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开。
实例:以下实例定义了函数 max(),参数为 num1, num2,用于比较两值的大小,并返回最大值。
function max(v1,v2)
local result = 0
if(v1 > v2) then
result = v1
else
result = v2
end
return result
end
print("两值比较,较大值为 "..max(10,5)) --输出:两值比较,较大值为 10
Lua中我们可以将函数作为参数传递给函数,如下实例:
myprint = function(param)
print("这是打印函数 - ##",param,"##")
end
function add(num1,num2,functionPrint)
result = num1 + num2
functionPrint(result) --调用传递的函数参数
end
myprint(10)
--myprint 函数作为参数传递
add(2,5,myprint)
--[[
输出结果:
这是打印函数 - ## 10 ##
这是打印函数 - ## 7 ##
--]]
多返回值
Lua函数可以返回多个结果值,比如string.find,其返回匹配串"开始和结束的下标"(如果不存在匹配串返回nil)。
s,e = string.find("This is chinese","chinese")
print(s,e) --输出:9 15
s,e = string.find("我是中国人","中国")
print(s,e) --输出:5 8
Lua函数中,在return后列出要返回的值的列表即可返回多值,如:
function maximum (a)
local k = 1 -- 最大值索引
local m = a[k] -- 最大值
for i,v in ipairs(a) do
if v > m then
k = i
m = v
end
end
return m, k
end
print(maximum({8,10,23,12,5})) --输出:23 3
可变参数
Lua函数可以接受可变数目的参数,和C语言类似,在函数参数列表中使用三点 ... 表示函数有可变的参数。
function sum(...)
local s = 0
for k,v in ipairs({...}) do
s = s + v
end
return s
end
print("总和 = "..sum(1,2,3,4,5)) --输出:总和 = 15
我们可以将可变参数赋值给一个变量。
例如,我们计算几个数的平均值:
function average(...)
result = 0
local arg={...} --> arg 为一个表,局部变量
for k,v in ipairs(arg) do
result = result + v
end
print("总共传入 " .. #arg .. " 个数")
return result/#arg
end
print("平均值为 = "..average(10,5,3,4,5,6))
--[[
输出结果:
总共传入 6 个数
平均值为 = 5.5
--]]
我们也可以通过select("#",...)来获取可变参数的数量:
function average(...)
result = 0
local arg={...}
for k,v in ipairs(arg) do
result = result + v
end
print("总共传入 " .. select("#",...) .. " 个数")
return result/select("#",...)
end
print("平均值为 = "..average(10,5,3,4,5,6))
--[[
输出结果:
总共传入 6 个数
平均值为 = 5.5
--]]
有时候我们可能需要几个固定参数加上可变参数,固定参数必须放在变长参数之前:
function myFun(str,...)
print(str)
local tab = {...}
for k,v in pairs(tab) do
print(k.." = "..)
end
end
myFun("我是固定参数1")
myFun("我是固定参数2",10,20,30)
--[[
输出结果:
我是固定参数1
我是固定参数2
1 = 10
2 = 20
3 = 30
--]]
通常在遍历可变参数的时候只需要使用 {…},然而可变参数可能会包含一些nil,那么就可以用select函数来访问可变参数了:select('#', …)或者select(n, …)
- select('#', …)返回可变参数的长度。
- select(n, …)用于返回从起点n开始到结束位置的所有参数列表。
调用select时,必须传入一个固定实参selector(选择开关)和一系列变长参数。如果selector为数字 n,那么select返回参数列表中从索引n开始到结束位置的所有参数列表,否则只能为字符串#,这样select返回变长参数的总数。
function myFun(...)
local v = select(3,...)
print(v)
print(select(3,...))
end
myFun(1,2,3,4,5)
--[[
输出结果:
3
3 4 5
--]]
function myFun(...)
for i=1,select("#",...) do
print("参数 = "..select(i,...))
end
end
myFun(1,2,3,4,5)
--[[
输出结果:
参数 = 1
参数 = 2
参数 = 3
参数 = 4
参数 = 5
--]]
Lua运算符
运算符是一个特殊的符号,用于告诉解释器执行特定的数学或逻辑运算。Lua提供了以下几种运算符类型:
- 算术运算符
- 关系运算符
- 逻辑运算符
- 其他运算符
算术运算符
下表列出了Lua语言中的常用算术运算符,设定A的值为10,B的值为 20:
操作符 | 描述 | 实例 |
---|---|---|
+ | 加法 | A + B 输出结果 30 |
- | 减法 | A - B 输出结果 -10 |
* | 乘法 | A * B 输出结果 200 |
/ | 除法 | B / A 输出结果 2 |
% | 取余 | B % A 输出结果 0 |
^ | 乘幂 | A^2 输出结果 100 |
- | 负号 | -A 输出结果 -10 |
// | 整除运算符(>=lua5.3) | 5//2 输出结果 2 |
在lua中,/用作除法运算,计算结果包含小数部分,//用作整除运算,计算结果不包含小数部分:
a = 5
b = 2
print("除法运算 - a/b 的值为 "..a / b)
print("整除运算 - a//b 的值为 "..a // b)
--[[
输出结果:
除法运算 - a/b 的值为 2.5
整除运算 - a//b 的值为 2
--]]
关系运算符
下表列出了Lua语言中的常用关系运算符,设定A的值为10,B的值为 20:
操作符 | 描述 | 实例 |
---|---|---|
== | 等于,检测两个值是否相等,相等返回true,否则返回false | (A == B)为false。 |
~= | 不等于,检测两个值是否相等,不相等返回true,否则返回 false | (A ~= B)为 true。 |
> | 大于,如果左边的值大于右边的值,返回true,否则返回false | (A > B)为 false。 |
< | 小于,如果左边的值大于右边的值,返回false,否则返回true | (A < B)为 true。 |
>= | 大于等于,如果左边的值大于等于右边的值,返回true,否则返回false | (A >= B)返回 false。 |
<= | 小于等于, 如果左边的值小于等于右边的值,返回true,否则返回false | (A <= B)返回 true。 |
逻辑运算符
下表列出了Lua语言中的常用逻辑运算符,设定A的值为true,B的值为false:
操作符 | 描述 | 实例 |
---|---|---|
and | 逻辑与操作符。 若A为false,则返回A,否则返回B。 | (A and B)为false。 |
or | 逻辑或操作符。 若A为true,则返回A,否则返回B。 | (A or B)为true。 |
not | 逻辑非操作符。与逻辑运算结果相反,如果条件为true,逻辑非为false。 | not(A and B)true。 |
其他运算符
下表列出了Lua语言中的连接运算符与计算表或字符串长度的运算符:
操作符 | 描述 | 实例 |
---|---|---|
.. | 连接两个字符串 | a..b ,其中a为 "Hello " , b为 "World", 输出结果为 "Hello World"。 |
# | 一元运算符,返回字符串或表的长度。 | #"Hello" 返回 5 |
运算符优先级
从高到低的顺序:
^
not - (unary)
* / %
+ -
..
< > <= >= ~= ==
and
or
除了^和..外所有的二元运算符都是左连接的。
a+i < b/2+1 <--> (a+i) < ((b/2)+1)
5+x^2*8 <--> 5+((x^2)*8)
a < y and y <= z <--> (a < y) and (y <= z)
-x^2 <--> -(x^2)
x^y^z <--> x^(y^z)
Lua字符串
字符串或串(String)是由数字、字母、下划线组成的一串字符。
在Lua中,字符串是一种基本的数据类型,用于存储文本数据。
Lua中的字符串可以包含任意字符,包括字母、数字、符号、空格以及其他特殊字符。
Lua语言中字符串可以使用以下三种方式来表示:
1.单引号间的一串字符
2.双引号间的一串字符
3.[[ 与 ]] 间的一串字符
字符串长度计算
在Lua中,要计算字符串的长度(即字符串中字符的个数),你可以使用string.len函数或utf8.len函数,包含中文的一般用utf8.len,string.len函数用于计算只包含ASCII字符串的长度。
str1 = "This is chinese"
str2 = "我是中国人"
print(string.len(str1)) --15
print(utf8.len(str2)) --10
以上实例的str1字符串只包含ASCII字符,因此string.len函数可以准确地返回字符串的长度。
包含中文的字符串使用utf8.len函数。
转义字符用于表示不能直接显示的字符,比如后退键,回车键等,如在字符串转换双引号可以使用 \。
所有的转义字符和所对应的意义:
转义字符 | 意义 | ASCII码值(十进制) |
\a | 响铃(BEL) | 007 |
\b | 退格(BS) ,将当前位置移到前一列 | 008 |
\f | 换页(FF),将当前位置移到下页开头 | 012 |
\n | 换行(LF) ,将当前位置移到下一行开头 | 010 |
\r | 回车(CR) ,将当前位置移到本行开头 | 013 |
\t | 水平制表(HT) (跳到下一个TAB位置) | 009 |
\v | 垂直制表(VT) | 011 |
\\ | 代表一个反斜线字符''\' | 092 |
\' | 代表一个单引号(撇号)字符 | 039 |
\" | 代表一个双引号字符 | 034 |
\0 | 空字符(NULL) | 000 |
\ddd | 1到3位八进制数所代表的任意字符 | 三位八进制 |
\xhh | 1到2位十六进制所代表的任意字符 | 二位十六进制 |
字符串操作
Lua提供了很多的方法来支持字符串的操作:
序号 | 方法 & 用途 |
---|---|
1 | string.upper(argument): 字符串全部转为大写字母。 |
2 | string.lower(argument): 字符串全部转为小写字母。 |
3 | string.gsub(mainString,findString,replaceString,num) 在字符串中替换。 mainString为要操作的字符串,findString为被替换的字符,replaceString要替换的字符,num替换次数(可以忽略,则全部替换),如:> string.gsub("AAAA","A","B",3); BBBA 3 |
4 | string.find (str, substr, [init, [plain]]) 在一个指定的目标字符串str中搜索指定的内容substr,如果找到了一个匹配的子串,就会返回这个子串的起始索引和结束索引,不存在则返回 nil。 init指定了搜索的起始位置,默认为1,可以一个负数,表示从后往前数的字符个数。 plain表示是否使用简单模式,默认为false,true只做简单的查找子串的操作,false表示使用使用正则模式匹配。 以下实例查找字符串 "Lua" 的起始索引和结束索引位置: > string.find("Hello Lua user", "Lua", 1) 7 9 |
5 | string.reverse(arg) 字符串反转 > string.reverse("Lua") auL |
6 | string.format(...) 返回一个类似printf的格式化字符串 > string.format("the value is:%d",4) the value is:4 |
7 | 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 > |
8 | string.len(arg) 计算字符串长度。 string.len("ABC") 3 |
9 | string.rep(string, n) 返回字符串string的n个拷贝 > string.rep("ABCD",2) ABCDABCD |
10 | .. 链接两个字符串 > print("www.runoob.".."com") www.runoob.com |
11 | string.gmatch(str, pattern) 返回一个迭代器函数,每一次调用这个函数,返回一个在字符串str找到的下一个符合pattern描述的子串。如果参数pattern描述的字符串没有找到,迭代函数返回nil。 > for word in string.gmatch("Hello Lua user", "%a+") do print(word) end Hello Lua user |
12 | string.match(str, pattern, init) string.match()只寻找源字串str中的第一个配对,参数init可选, 指定搜寻过程的起点, 默认为1。 在成功配对时, 函数将返回配对表达式中的所有捕获结果; 如果没有设置捕获标记, 则返回整个配对字符串。当没有成功的配对时, 返回nil。 > = string.match("I have 2 questions for you.", "%d+ %a+") 2 questions > = string.format("%d, %q", string.match("I have 2 questions for you.", "(%d+) (%a+)")) 2, "questions" |
字符串截取
字符串截取使用sub()方法。
string.sub()用于截取字符串,原型为:
string.sub(s, i [, j])
参数说明:
- s:要截取的字符串。
- i:截取开始位置。
- j:截取结束位置,默认为 -1,最后一个字符。
字符串大小写转换
字符串查找与反转
字符串格式化
Lua提供了string.format()函数来生成具有特定格式的字符串, 函数的第一个参数是格式 , 之后是对应格式中每个代号的各种数据。
由于格式字符串的存在, 使得产生的长字符串可读性大大提高了。这个函数的格式很像C语言中的 printf()。
以下实例演示了如何对字符串进行格式化操作:
格式字符串可能包含以下的转义码:
- %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, 若后接f(浮点数转义符, 如%6.3f)则设定该浮点数的小数只保留n位, 若后接s(字符串转义符, 如%5.3s)则设定该字符串只显示前n位.
string1 = "Lua"
string2 = "Tutorial"
number1 = 10
number2 = 20
-- 基本字符串格式化
print(string.format("基本格式化 %s %s",string1,string2))
-- 日期格式化
date = 2; month = 1; year = 2014
print(string.format("日期格式化 %02d/%02d/%03d", date, month, year))
-- 十进制格式化
print(string.format("%.4f",1/3))
--[[
输出结果:
基本格式化 Lua Tutorial
日期格式化 02/01/2014
0.3333
--]]
string.format("%c", 83) -- 输出S
string.format("%+d", 17.0) -- 输出+17
string.format("%05d", 17) -- 输出00017
string.format("%o", 17) -- 输出21
string.format("%u", 3.14) -- 输出3
string.format("%x", 13) -- 输出d
string.format("%X", 13) -- 输出D
string.format("%e", 1000) -- 输出1.000000e+03
string.format("%E", 1000) -- 输出1.000000E+03
string.format("%6.3f", 13) -- 输出13.000
string.format("%q", "One\nTwo") -- 输出"One\
-- Two"
string.format("%s", "monkey") -- 输出monkey
string.format("%10s", "monkey") -- 输出 monkey
string.format("%5.3s", "monkey") -- 输出 mon
字符与整数相互转换
以下实例演示了字符与整数相互转换:
-- 字符转换
-- 转换第一个字符
print(string.byte("Lua"))
-- 转换第三个字符
print(string.byte("Lua",3))
-- 转换末尾第一个字符
print(string.byte("Lua",-1))
-- 第二个字符
print(string.byte("Lua",2))
-- 转换末尾第二个字符
print(string.byte("Lua",-2))
-- 整数 ASCII 码转换为字符
print(string.char(97))
--[[
输出结果:
76
97
97
117
117
a
--]]
其他常用函数
以下实例演示了其他字符串操作,如计算字符串长度,字符串连接,字符串复制等:
string1 = "www."
string2 = "runoob"
string3 = ".com"
-- 使用 .. 进行字符串连接
print("连接字符串",string1..string2..string3)
-- 字符串长度
print("字符串长度 ",string.len(string2))
-- 字符串复制 2 次
repeatedString = string.rep(string2,2)
print(repeatedString)
--[[
输出结果:
连接字符串 www.runoob.com
字符串长度 6
runoobrunoob
--]]
匹配模式
Lua中的匹配模式直接用常规的字符串来描述。 它用于模式匹配函数string.find, string.gmatch, string.gsub, string.match。
你还可以在模式串中使用字符类。
字符类指可以匹配一个特定字符集合内任何字符的模式项。比如,字符类%d匹配任意数字。所以你可以使用模式串%d%d/%d%d/%d%d%d%d搜索dd/mm/yyyy格式的日期:
s = "Deadline is 30/05/1999, firm"
date = "%d%d/%d%d/%d%d%d%d"
print(string.sub(s, string.find(s, date))) --> 30/05/1999
下面的表列出了Lua支持的所有字符类:
单个字符(除 ^$()%.[]*+-? 外): 与该字符自身配对
- .(点): 与任何字符配对
- %a: 与任何字母配对
- %c: 与任何控制符配对(例如\n)
- %d: 与任何数字配对
- %l: 与任何小写字母配对
- %p: 与任何标点(punctuation)配对
- %s: 与空白字符配对
- %u: 与任何大写字母配对
- %w: 与任何字母/数字配对
- %x: 与任何十六进制数配对
- %z: 与任何代表0的字符配对
- %x(此处x是非字母非数字字符): 与字符x配对. 主要用来处理表达式中有功能的字符(^$()%.[]*+-?)的配对问题, 例如%%与%配对
- [数个字符类]: 与任何[]中包含的字符类配对. 例如[%w_]与任何字母/数字, 或下划线符号(_)配对
- [^数个字符类]: 与任何不包含在[]中的字符类配对. 例如[^%s]与任何非空白字符配对
当上述的字符类用大写书写时, 表示与非此字符类的任何字符配对. 例如, %S表示与任何非空白字符配对.例如,'%A'非字母的字符:
print(string.gsub("hello, up-down!", "%A", "."))
--输出:hello..up.down. 4
数字4不是字符串结果的一部分,他是gsub返回的第二个结果,代表发生替换的次数。
在模式匹配中有一些特殊字符,他们有特殊的意义,Lua中的特殊字符如下:
( ) . % + - * ? [ ^ $
'%' 用作特殊字符的转义字符,因此 '%.' 匹配点;'%%' 匹配字符 '%'。转义字符 '%'不仅可以用来转义特殊字符,还可以用于所有的非字母的字符。
模式条目可以是:
- 单个字符类匹配该类别中任意单个字符;
- 单个字符类跟一个 '
*
', 将匹配零或多个该类的字符。 这个条目总是匹配尽可能长的串; - 单个字符类跟一个 '
+
', 将匹配一或更多个该类的字符。 这个条目总是匹配尽可能长的串; - 单个字符类跟一个 '
-
', 将匹配零或更多个该类的字符。 和 '*
' 不同, 这个条目总是匹配尽可能短的串; - 单个字符类跟一个 '
?
', 将匹配零或一个该类的字符。 只要有可能,它会匹配一个; %n
, 这里的 n 可以从 1 到 9; 这个条目匹配一个等于 n 号捕获物(后面有描述)的子串。%bxy
, 这里的 x 和 y 是两个明确的字符; 这个条目匹配以 x 开始 y 结束, 且其中 x 和 y 保持 平衡 的字符串。 意思是,如果从左到右读这个字符串,对每次读到一个 x 就 +1 ,读到一个 y 就 -1, 最终结束处的那个 y 是第一个记数到 0 的 y。 举个例子,条目%b()
可以匹配到括号平衡的表达式。%f[set]
, 指 边境模式; 这个条目会匹配到一个位于 set 内某个字符之前的一个空串, 且这个位置的前一个字符不属于 set 。 集合 set 的含义如前面所述。 匹配出的那个空串之开始和结束点的计算就看成该处有个字符 '\0
' 一样。
模式:
模式 指一个模式条目的序列。 在模式最前面加上符号 '^
' 将锚定从字符串的开始处做匹配。 在模式最后面加上符号 '$
' 将使匹配过程锚定到字符串的结尾。 如果 '^
' 和 '$
' 出现在其它位置,它们均没有特殊含义,只表示自身。
捕获:
模式可以在内部用小括号括起一个子模式; 这些子模式被称为 捕获物。 当匹配成功时,由 捕获物 匹配到的字符串中的子串被保存起来用于未来的用途。 捕获物以它们左括号的次序来编号。 例如,对于模式 "(a*(.)%w(%s*))"
, 字符串中匹配到 "a*(.)%w(%s*)"
的部分保存在第一个捕获物中 (因此是编号 1); 由 ".
" 匹配到的字符是 2 号捕获物, 匹配到 "%s*
" 的那部分是 3 号。
作为一个特例,空的捕获 ()
将捕获到当前字符串的位置(它是一个数字)。 例如,如果将模式 "()aa()"
作用到字符串 "flaaap"
上,将产生两个捕获物: 3 和 5 。
Lua数组
数组,就是相同数据类型的元素按一定顺序排列的集合,可以是一维数组和多维数组。
在Lua中,数组不是一种特定的数据类型,而是一种用来存储一组值的数据结构。
实际上,Lua中并没有专门的数组类型,而是使用一种被称为 "table" 的数据结构来实现数组的功能。
Lua数组的索引键值可以使用整数表示,数组的大小不是固定的。
在Lua索引值是以1为起始,但你也可以指定0开始。
一维数组
一维数组是最简单的数组,其逻辑结构是线性表。
获取数组中的元素个数:使用#操作符
向数组中添加元素:myTable[#myTable+1] = 值
删除数组中的元素:table.remove(myTable,index)
myTable = {10,20,30,40,50}
for k,v in ipairs(myTable) do
print(k.." = "..v)
end
print("数组元素个数:"..#myTable) --打印数组元素个数
myTable[#myTable+1] = 100 --添加元素
for k,v in ipairs(myTable) do
print(k.." = "..v)
end
table.remove(myTable,2) --删除元素
for k,v in ipairs(myTable) do
print(k.." = "..v)
end
注意:使用#操作符来获取带有非整数键的数组时,会自动忽略掉这些键值对。例如
myTable = {1,2,3,key = 4,5}
print(#myTable) --输出4
多维数组
多维数组即数组中包含数组或一维数组的索引键对应一个数组。
以下是一个三行三列的阵列多维数组:
--初始化数组
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
不同索引键的三行三列阵列多维数组:
--初始化数组
array = {}
maxRows = 3
maxColumns = 3
for row=1,maxRows do
for col=1,maxColumns do
array[row*maxColumns +col] = row*col
end
end
--访问数组
for row=1,maxRows do
for col=1,maxColumns do
print(array[row*maxColumns +col])
end
end
Lua迭代器
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。
在Lua中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。
泛型for迭代器
泛型for在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。
泛型for迭代器提供了集合的key/value对,语法格式如下:
for k,v in pairs(myTable) do
print(k,v)
end
-- 或者
for k,v in ipairs(myTable) do
print(k,v)
end
上面代码中,k, v为变量列表;pairs(myTable)为表达式列表。
查看以下实例 :
array = {"Google", "Runoob"}
for key,value in ipairs(array)
do
print(key, value)
end
--[[
以上代码执行输出结果为:
1 Google
2 Runoob
--]]
以上实例中我们使用了Lua默认提供的迭代函数ipairs。
下面我们看看泛型for的执行过程:
- 首先,初始化,计算in后面表达式的值,表达式应该返回泛型for需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会被忽略。
- 第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于for结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
- 第三,将迭代函数返回的值赋给变量列表。
- 第四,如果返回的第一个值为nil循环结束,否则执行循环体。
- 第五,回到第二步再次调用迭代函数
在Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。Lua的迭代器包含以下两种类型:
- 无状态的迭代器
- 多状态的迭代器
无状态的迭代器
无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。
每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。
这种无状态迭代器的典型的简单的例子是ipairs,它遍历数组的每一个元素,元素的索引需要是数值。
以下实例我们使用了一个简单的函数来实现迭代器,实现数字n的平方:
function square(iteratorMaxCount,currentNumber)
if(currentNumber<iteratorMaxCount) then
currentNumber = currentNumber+1
return currentNumber, currentNumber*currentNumber
end
end
for k,v in square,3,0 do
print(k,v)
end
--[[
以上实例输出结果为:
1 1
2 4
3 9
--]]
迭代的状态包括被遍历的表(循环过程中不会改变的状态常量)和当前的索引下标(控制变量),ipairs 和迭代函数都很简单,我们在Lua 中可以这样实现:
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"}
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
--[[
以上实例输出结果为:
Google
Runoob
--]]
以上实例中我们可以看到,elementIterator内使用了闭包函数,实现计算集合大小并输出各个元素。
Lua table(表)
table是Lua的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。
Lua table使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是nil。
Lua table是不固定大小的,你可以根据自己需要进行扩容。
Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。例如string.format表示使用"format"来索引table string。
table(表)的构造
构造器是创建和初始化表的表达式。表是Lua特有的功能强大的东西。最简单的构造函数是{},用来创建一个空表。可以直接初始化数组:
- 初始化表
mytable = {}
-- 指定值
mytable[1]= "Lua"
-- 移除引用
mytable = nil
-- lua 垃圾回收会释放内存
当我们为table a并设置元素,然后将a赋值给b,则a与b都指向同一个内存。如果a设置为nil ,则b同样能访问table的元素。如果没有指定的变量指向a,Lua的垃圾回收机制会清理相对应的内存。
以下实例演示了以上的描述情况:
-- 简单的 table
mytable = {}
print("mytable 的类型是 ",type(mytable))
mytable[1]= "Lua"
mytable["wow"] = "修改前"
print("mytable 索引为 1 的元素是 ", mytable[1])
print("mytable 索引为 wow 的元素是 ", mytable["wow"])
-- alternatetable和mytable的是指同一个 table
alternatetable = mytable
print("alternatetable 索引为 1 的元素是 ", alternatetable[1])
print("mytable 索引为 wow 的元素是 ", alternatetable["wow"])
alternatetable["wow"] = "修改后"
print("mytable 索引为 wow 的元素是 ", mytable["wow"])
-- 释放变量
alternatetable = nil
print("alternatetable 是 ", alternatetable)
-- mytable 仍然可以访问
print("mytable 索引为 wow 的元素是 ", mytable["wow"])
mytable = nil
print("mytable 是 ", mytable)
--[[
以上代码执行结果为:
mytable 的类型是 table
mytable 索引为 1 的元素是 Lua
mytable 索引为 wow 的元素是 修改前
alternatetable 索引为 1 的元素是 Lua
mytable 索引为 wow 的元素是 修改前
mytable 索引为 wow 的元素是 修改后
alternatetable 是 nil
mytable 索引为 wow 的元素是 修改后
mytable 是 nil
--]]
table操作
以下列出了table操作常用的方法:
序号 | 方法 & 用途 |
---|---|
1 | table.concat (table [, sep [, start [, end]]]): concat是concatenate(连锁, 连接)的缩写. table.concat()函数列出参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep)隔开。 |
2 | table.insert (table, [pos,] value): 在table的数组部分指定位置(pos)插入值为value的一个元素. pos参数可选, 默认为数组部分末尾. |
3 | table.maxn (table) 指定table中所有整数key值中最大的key值. 如果不存在key值为整数的元素, 则返回0。(Lua5.2之后该方法已经不存在了,本文使用了自定义函数实现) |
4 | table.remove (table [, pos]) 返回table数组部分位于pos位置的元素. 其后的元素会被前移. pos参数可选, 默认为table长度, 即从最后一个元素删起。 |
5 | table.sort (table [, comp]) 对给定的table进行升序排序。 |
接下来我们来看下这几个方法的实例。
连接:table.concat()
我们可以使用table.concat()输出一个列表中元素连接成的字符串:
fruits = {"banana","orange","apple"}
-- 返回table连接后的字符串
print("连接后的字符串 ",table.concat(fruits))
-- 指定连接字符
print("连接后的字符串 ",table.concat(fruits,", "))
-- 指定索引来连接table
print("连接后的字符串 ",table.concat(fruits,", ", 2,3))
--[[
执行以上代码输出结果为:
连接后的字符串 bananaorangeapple
连接后的字符串 banana, orange, apple
连接后的字符串 orange, apple
--]]
插入和移除:insert()、remove()
以下实例演示了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])
--[[
执行以上代码输出结果为:
索引为 4 的元素为 mango
索引为 2 的元素为 grapes
最后一个元素为 mango
移除后最后一个元素为 nil
--]]
排序 :table.sort()
以下实例演示了table.sort()方法的使用,用于对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
--[[
执行以上代码输出结果为:
排序前
1 banana
2 orange
3 apple
4 grapes
排序后
1 apple
2 banana
3 grapes
4 orange
--]]
最大值:table.maxn()
table.maxn在Lua5.2之后该方法已经不存在了,我们定义了table_maxn方法来实现。
以下实例演示了如何获取table中的最大值:
function table_maxn(t)
local mn=nil;
for k, v in pairs(t) do
if(mn==nil) then
mn=v
end
if mn < v then
mn = v
end
end
return mn
end
tbl = {[1] = 2, [2] = 6, [3] = 34, [26] =5}
print("tbl 最大值:", table_maxn(tbl))
print("tbl 长度 ", #tbl)
--[[
执行以上代码输出结果为:
tbl 最大值: 34
tbl 长度 3
--]]
注意:
当我们获取table的长度的时候无论是使用#还是table.getn其都会在索引中断的地方停止计数,而导致无法正确取得table的长度。
可以使用以下方法来代替:
function table_leng(t)
local leng=0
for k, v in pairs(t) do
leng=leng+1
end
return leng;
end
Lua模块与包
模块类似于一个封装库,从Lua 5.1开始,Lua加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以API接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
Lua的模块是由变量、函数等已知元素组成的table,因此创建一个模块很简单,就是创建一个table,然后把需要导出的常量、函数放入其中,最后返回这个table就行。以下为创建自定义模块module.lua,文件代码格式如下:
-- 文件名为 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
因此可以像操作调用table里的元素那样来操作调用模块里的常量或函数。
上面的func2声明为程序块的局部变量,即表示一个私有函数,因此是不能从外部访问模块里的这个私有函数,必须通过模块里的公有函数来调用。
require函数
Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:
require("<模块名>")或者require "<模块名>"
执行require后会返回一个由模块常量或函数组成的table,并且还会定义一个包含该table的全局变量。
-- my_module.lua文件
-- module模块为上文提到到module.lua
require("module")
print(module.constant)
module.func3()
--[[
以上代码执行结果为:
这是一个常量
这是一个私有函数!
--]]
或者给加载的模块定义一个别名变量,方便调用:
-- my_module2.lua文件
-- module模块为上文提到到module.lua
-- 别名变量 m
local m = require("module")
print(m.constant)
m.func3()
--[[
以上代码执行结果为:
这是一个常量
这是一个私有函数!
--]]
加载机制
对于自定义的模块,模块文件不是放在哪个文件目录都行,函数require有它自己的文件路径加载策略,它会尝试从Lua文件或C程序库中加载模块。
require用于搜索Lua文件的路径是存放在全局变量package.path中,当Lua启动后,会以环境变量 LUA_PATH的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。
当然,如果没有LUA_PATH这个环境变量,也可以自定义设置,在当前用户根目录下打开.profile 文件(没有则创建,打开.bashrc文件也可以),例如把"~/lua/"路径加入LUA_PATH环境变量里:
#LUA_PATH
export LUA_PATH="~/lua/?.lua;;"
文件路径以";"号分隔,最后的2个";;"表示新加的路径后面加上原来的默认路径。
接着,更新环境变量参数,使之立即生效。
source ~/.profile
这时假设package.path的值是:
/Users/dengjoe/lua/?.lua;./?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/lib/lua/5.1/?.lua;/usr/local/lib/lua/5.1/?/init.lua
那么调用require("module")时就会尝试打开以下文件目录去搜索目标。
/Users/dengjoe/lua/module.lua; ./module.lua /usr/local/share/lua/5.1/module.lua /usr/local/share/lua/5.1/module/init.lua /usr/local/lib/lua/5.1/module.lua /usr/local/lib/lua/5.1/module/init.lua
如果找过目标文件,则会调用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)就是"元方法"。
有两个很重要的函数来处理元表:
- setmetatable(table,metatable): 对指定table设置元表(metatable),如果元表(metatable)中存在__metatable键值,setmetatable会失败。
- getmetatable(table): 返回对象的元表(metatable)。
以下实例演示了如何对指定的表设置元表:
mytable = {} -- 普通表
mymetatable = {} -- 元表
setmetatable(mytable,mymetatable) -- 把mymetatable设为mytable的元表
以上代码也可以直接写成一行:mytable = setmetatable({},{})
以下为返回对象元表:getmetatable(mytable) -- 这会返回mymetatable
__index元方法
这是metatable最常用的键。
当你通过键来访问table的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index键。如果__index包含一个表格,Lua会在表格中查找相应的键。
我们可以在使用lua脚本式编程模式查看:
myTable = {key1=10,key2=20}
newTable = setmetatable({},{__index = myTable})
print(newTable.key1) --输出10
print(newTable.key3) --输出nil
如果__index包含一个函数的话,Lua就会调用那个函数,table和键会作为参数传递给函数。
__index元方法查看表中元素是否存在,如果不存在,返回结果为nil;如果存在则由 __index返回结果。
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
实例解析:
-
mytable表赋值为 {key1 = "value1"}。
-
mytable设置了元表,元方法为 __index。
-
在mytable表中查找key1,如果找到,返回该元素,找不到则继续。
-
在mytable表中查找key2,如果找到,返回metatablevalue,找不到则继续。
-
判断元表有没有__index方法,如果__index方法是一个函数,则调用该函数。
-
元方法中查看是否传入"key2"键的参数(mytable.key2已设置),如果传入"key2"参数返回"metatablevalue",否则返回mytable对应的键值。
我们可以将以上代码简单写成:
mytable = setmetatable({key1 = "value1"}, { __index = { key2 = "metatablevalue" } })
print(mytable.key1,mytable.key2)
总结
Lua查找一个表元素时的规则,其实就是如下 3 个步骤:
- 1.在表中查找,如果找到,返回该元素,找不到则继续
- 2.判断该表是否有元表,如果没有元表,返回nil,有元表则继续。
- 3.判断元表有没有__index方法,如果__index方法为nil,则返回nil;如果__index方法是一个表,则重复1、2、3;如果__index方法是一个函数,则返回该函数的返回值。
__newindex元方法
__newindex元方法用来更新表,__index则用来访问表。
当你给表的一个缺少的索引赋值,解释器就会查找__newindex元方法:如果存在则调用这个函数而不进行赋值操作。
以下实例演示了__newindex元方法的应用:
mymetatable = {}
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })
print(mytable.key1)
mytable.newkey = "新值2"
print(mytable.newkey,mymetatable.newkey)
mytable.key1 = "新值1"
print(mytable.key1,mymetatable.key1)
--[[
以上实例执行输出结果为:
value1
nil 新值2
新值1 nil
--]]
以上实例中表设置了元方法__newindex,在对新索引键(newkey)赋值时(mytable.newkey = "新值2"),会调用元方法,而不进行赋值。而如果对已存在的索引键(key1),则会进行赋值,而不调用元方法__newindex。
以下实例使用了rawset函数来更新表:
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,即返回表最大键值
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
--[[
以上实例执行输出结果为:
1 1
2 2
3 3
4 4
5 5
6 6
--]]
__add键包含在元表中,并进行相加操作。 表中对应的操作列表如下:(注意:__是两个下划线)
模式 | 描述 |
---|---|
__add | 对应的运算符 '+'. |
__sub | 对应的运算符 '-'. |
__mul | 对应的运算符 '*'. |
__div | 对应的运算符 '/'. |
__mod | 对应的运算符 '%'. |
__unm | 对应的运算符 '-'. |
__concat | 对应的运算符 '..'. |
__eq | 对应的运算符 '=='. |
__lt | 对应的运算符 '<'. |
__le | 对应的运算符 '<='. |
__call元方法
_call元方法在Lua调用一个值时调用。以下实例演示了计算表中元素的和:
-- 计算表中最大值,table.maxn在Lua5.2以上版本中已无法使用
-- 自定义计算表中最大键值函数table_maxn,即计算表的元素个数
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代码。
Lua协同程序(coroutine)
什么是协同(coroutine)?
Lua协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。
协同程序可以理解为一种特殊的线程,可以暂停和恢复其执行,从而允许非抢占式的多任务处理。
协同是非常强大的功能,但是用起来也很复杂。
基本语法
协同程序由coroutine模块提供支持。
使用协同程序,你可以在函数中使用coroutine.create创建一个新的协同程序对象,并使用coroutine.resume启动它的执行。协同程序可以通过调用coroutine.yield来主动暂停自己的执行,并将控制权交还给调用者。
方法 | 描述 |
---|---|
coroutine.create() | 创建 coroutine,返回 coroutine, 参数是一个函数,当和 resume 配合使用的时候就唤醒函数调用 |
coroutine.resume() | 重启 coroutine,和 create 配合使用 |
coroutine.yield() | 挂起 coroutine,将 coroutine 设置为挂起状态,这个和 resume 配合使用能有很多有用的效果 |
coroutine.status() | 查看 coroutine 的状态 注:coroutine 的状态有三种:dead,suspended,running,具体什么时候有这样的状态请参考下面的程序 |
coroutine.wrap() | 创建 coroutine,返回一个函数,一旦你调用这个函数,就进入 coroutine,和 create 功能重复 |
coroutine.running() | 返回正在跑的 coroutine,一个 coroutine 就是一个线程,当使用running的时候,就是返回一个 coroutine 的线程号 |
以下实例演示了如何使用Lua协同程序:
function speak()
print("协同程序 speak 开始执行")
local value = coroutine.yield("挂起协同程序 speak 暂停执行")
print("协同程序 speak 恢复执行,传入的值为:"..tostring(value))
print("协同程序 speak 结束执行")
end
local co = coroutine.create(speak) --创建协同程序
-- 启动协同程序
local status,result = coroutine.resume(co)
print(status,result)
-- 恢复协同程序
status,result = coroutine.resume(co,10)
print(status,result)
--[[
输出结果:
协同程序 speak 开始执行
true 挂起协同程序 speak 暂停执行
协同程序 speak 恢复执行,传入的值为:10
协同程序 speak 结束执行
true nil
--]]
以上实例中,我们定义了一个名为speak的函数作为协同程序。在函数中,我们使用coroutine.yield暂停了协同程序的执行,并返回了一个值。在主程序中,我们使用coroutine.create创建了一个协同程序对象,并使用coroutine.resume启动了它的执行。
在第一次调用coroutine.resume后,协同程序执行到coroutine.yield处暂停,并将值返回给主程序。然后,我们再次调用coroutine.resume,并传入一个值作为协同程序恢复执行时的参数。
需要注意的是,协同程序的状态可以通过coroutine.status函数获取,通过检查状态可以确定协同程序的执行情况(如运行中、已挂起、已结束等)。
以下实例演示了以上各个方法的用法:
co1 = coroutine.create(
function(value)
print("传入的值为:"..value)
end
)
coroutine.resume(co1,10) --输出:传入的值为:10
print(coroutine.status(co1)) --输出:dead
print("--------------------")
-- 使用coroutine.wrap创建了一个协同程序包装器,将协同程序转换为一个可直接调用的函数对象。
co1 = coroutine.wrap(
function(value)
print("传入的值为:"..value)
end
)
co1(20) --输出:传入的值为:20
print("--------------------")
co2 = coroutine.create(
function()
for i=1,10 do
print(i)
if i == 3 then
print(coroutine.status(co2))
print(coroutine.running())
end
coroutine.yield("第"..i.."次,挂起协同程序")
end
end
)
status,result = coroutine.resume(co2) --输出:1
print(status,result) --输出:
status,result = coroutine.resume(co2) --输出:2
print(status,result) --输出:
status,result = coroutine.resume(co2) --输出:3
print(status,result) --输出:
-- 通过coroutine.status检查协同程序co2的状态,输出为suspended,表示协同程序暂停执行。
print(coroutine.status(co2)) --输出:suspended
print(coroutine.running()) --输出:nil
--[[
输出结果:
传入的值为:10
dead
--------------------
传入的值为:20
--------------------
1
true 第1次,挂起协同程序
2
true 第2次,挂起协同程序
3
running
thread: 00AC8628
true 第3次,挂起协同程序
suspended
nil
--]]
从coroutine.running就可以看出来,coroutine在底层实现就是一个线程。
当create一个coroutine的时候就是在新线程中注册了一个事件。
当使用resume触发事件的时候,create的coroutine函数就被执行了,当遇到yield的时候就代表挂起当前线程,等候再次resume触发事件。
详细实例:
function speak(value)
print("speak 函数开始执行,传入的值为:"..value)
return coroutine.yield(value*2)
end
co = coroutine.create(
function(v1,v2)
print("第一次执行协同程序,传入的值为:"..v1.."和"..v2)
local value = speak(v1)
print("第二次执行协同程序,传入的值为:"..value)
local r,s = coroutine.yield(v1+v2,v1-v2)
print("第三次执行协同程序,传入的值为:"..r.."和"..s)
return v2,"结束协同程序"
end
)
print(coroutine.resume(co, 1, 10)) -- true 2
print("--------------------")
print(coroutine.resume(co, "r")) -- true 11 -9
print("--------------------")
print(coroutine.resume(co, "x", "y")) -- true 10 结束协同程序
print("--------------------")
print(coroutine.resume(co, "x", "y")) -- false cannot resume dead coroutine
--[[
输出结果:
第一次执行协同程序,传入的值为:1和10
speak 函数开始执行,传入的值为:1
true 2
--------------------
第二次执行协同程序,传入的值为:r
true 11 -9
--------------------
第三次执行协同程序,传入的值为:x和y
true 10 结束协同程序
--------------------
false cannot resume dead coroutine
--]]
以上实例步骤如下:
- 调用resume,将协同程序唤醒,resume操作成功返回true,否则返回false;
- 协同程序运行;
- 运行到yield语句;
- yield挂起协同程序,第一次resume返回;(注意:此处yield返回,参数是resume的参数)
- 第二次resume,再次唤醒协同程序;(注意:此处resume的参数中,除了第一个参数,剩下的参数将作为yield的参数)
- yield返回;
- 协同程序继续运行;
- 如果使用的协同程序继续运行完成后继续调用resume方法则输出:cannot resume dead coroutine
resume和yield的配合强大之处在于,resume处于主程中,它将外部状态(数据)传入到协同程序内部;而yield则将内部的状态(数据)返回到主程中。
生产者-消费者案例
local co_productor
function productor() --生产者
local i = 0
while true do
i = i + 1
coroutine.yield("物品:"..i) --将生产的物品发送给消费者
end
end
function consumer(value) --消费者
local i = 0
while(i < value) do
i = i + 1
local status,result = coroutine.resume(co_productor) --启动协同程序
print(status,result)
end
end
co_productor = coroutine.create(productor) --创建协同程序
consumer(10) --消费10个物品
--[[
输出结果:
true 物品:1
true 物品:2
true 物品:3
true 物品:4
true 物品:5
true 物品:6
true 物品:7
true 物品:8
true 物品:9
true 物品:10
--]]
线程和协同程序的区别
线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。
在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起。
协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。
主要区别归纳如下:
- 调度方式:线程通常由操作系统的调度器进行抢占式调度,操作系统会在不同线程之间切换执行权。而协同程序是非抢占式调度的,它们由程序员显式地控制执行权的转移。
- 并发性:线程是并发执行的,多个线程可以同时运行在多个处理器核心上,或者通过时间片轮转在单个核心上切换执行。协同程序则是协作式的,只有一个协同程序处于运行状态,其他协同程序必须等待当前协同程序主动放弃执行权。
- 内存占用:线程通常需要独立的堆栈和上下文环境,因此线程的创建和销毁会带来额外的开销。而协同程序可以共享相同的堆栈和上下文,因此创建和销毁协同程序的开销较小。
- 数据共享:线程之间可以共享内存空间,但需要注意线程安全性和同步问题。协同程序通常通过参数传递和返回值来进行数据共享,不同协同程序之间的数据隔离性较好。
- 调试和错误处理:线程通常在调试和错误处理方面更复杂,因为多个线程之间的交互和并发执行可能导致难以调试的问题。协同程序则在调试和错误处理方面相对简单,因为它们是由程序员显式地控制执行流程的。
总体而言,线程适用于需要并发执行的场景,例如在多核处理器上利用并行性加快任务的执行速度。而协同程序适用于需要协作和协调的场景,例如状态机、事件驱动编程或协作式任务处理。选择使用线程还是协同程序取决于具体的应用需求和编程模型。
Lua文件I/O
Lua I/O库用于读取和处理文件。分为简单模式(和C一样)、完全模式。
- 简单模式(simple model)拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。
- 完全模式(complete model)使用外部的文件句柄来实现。它以一种面向对象的形式,将所有的文件操作定义为文件句柄的方法。
简单模式在做一些简单的文件操作时较为合适。但是在进行一些高级的文件操作的时候,简单模式就显得力不从心。例如同时读取多个文件这样的操作,使用完全模式则较为合适。
打开文件操作语句如下:
file = io.open(filename[,mode])
mode的值有:
r:以只读方式打开文件,该文件必须存在。
w:打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
a:以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
r+:以可读写方式打开文件,该文件必须存在。
w+:打开可读写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
a+:以附加的方式打开可读写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留)
b:二进制模式(如果文件是二进制文件)。
+:表示对文件既可以读也可以写。
简单模式
简单模式使用标准的I/O或使用一个当前输入文件和一个当前输出文件。
以下为 LuaStudy.lua 文件代码,操作的文件为file.lua(如果没有需要创建该文件),代码如下:
file = io.open("file.lua","r") -- 以只读方式打开文件
io.input(file) -- 设置默认输入文件为 file.lua
print(io.read()) -- 输出文件第一行
io.close(file) -- 关闭打开的文件
file = io.open("file.lua","a") -- 以附加的方式打开只写文件
io.output(file) -- 设置默认输出文件为 file.lua
io.write("--新添加的内容") -- 在文件最后一行添加 Lua 注释
io.close() -- 关闭打开的文件
执行完以上代码后,file.lua的文件内容为:
--大家好啊
--新添加的内容
在以上实例中我们使用了io."x"方法,其中io.read()中我们没有带参数,参数可以是下表中的一个:
模式 | 描述 |
---|---|
"*n" | 读取一个数字并返回它。例:file.read("*n") |
"*a" | 从当前位置读取整个文件。例:file.read("*a") |
"*l"(默认) | 读取下一行,在文件尾(EOF)处返回nil。例:file.read("*l") |
number | 返回一个指定字符个数的字符串,或在EOF时返回nil。例:file.read(5) |
其他的io方法有:
-
io.tmpfile():返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除。
-
io.type(file): 检测obj是否一个可用的文件句柄。
-
io.flush(): 向文件写入缓冲中的所有数据。
-
io.lines(optional file name): 返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,但不关闭文件。
完全模式
通常我们需要在同一时间处理多个文件。我们需要使用file:function_name来代替io.function_name方法。以下实例演示了如何同时处理同一个文件:
file = io.open("file.lua","r") -- 以只读方式打开文件
print(file:read()) -- 输出文件第一行
file:close() -- 关闭打开的文件
file = io.open("file.lua","a") -- 以附加的方式打开只写文件
file:write("--又添加了新的文件内容") - 在文件最后一行添加 Lua 注释
file:close() -- 关闭打开的文件
执行完以上代码后,file.lua的文件内容为:
--大家好啊
--新添加的内容
--又添加了新的文件内容
read的参数与简单模式一致。
其他方法:
-
file:seek(optional whence, optional offset): 设置和获取当前文件位置,成功则返回最终的文件位置(按字节),失败则返回nil加错误信息。参数whence值可以是:
- "set":从文件头开始
- "cur":从当前位置开始[默认]
- "end":从文件尾开始
- offset:默认为0
-
file:flush(): 向文件写入缓冲中的所有数据
-
io.lines(optional file name): 打开指定的文件filename为读模式并返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,并自动关闭文件。
若不带参数时io.lines() <=> io.input():lines(); 读取默认输入设备的内容,但结束时不关闭文件,如:for line in io.lines("file.lua") do print(line) end
以下实例使用了seek方法,定位到文件倒数第15个位置并使用read方法的*a参数,即从当前位置(倒数第15个位置)读取整个文件。
file = io.open("file.lua", "r") -- 以只读方式打开文件
file:seek("end",-15)
print(file:read("*a"))
file:close() -- 关闭打开的文件
Lua错误处理
程序运行中错误处理是必要的,在我们进行文件操作,数据转移及web service调用过程中都会出现不可预期的错误。如果不注重错误信息的处理,就会造成信息泄露,程序无法运行等情况。
任何程序语言中,都需要错误处理。错误类型有:
- 语法错误
- 运行错误
语法错误
运行错误
错误处理
我们可以使用两个函数:assert和error来处理错误。实例如下:
local function add(a,b)
assert(type(a) == "number", "a 不是一个数字")
assert(type(b) == "number", "b 不是一个数字")
return a+b
end
add(10)
--[[
输出结果:
lua: LuaStudy.lua:356: b 不是一个数字
stack traceback:
[C]: in function 'assert'
LuaStudy.lua:356: in function 'add'
LuaStudy.lua:359: in main chunk
[C]: ?
--]]
实例中assert首先检查第一个参数,若没问题,assert不做任何事情;否则,assert以第二个参数作为错误信息抛出。
error函数
语法格式:
error (message [, level])
功能:终止正在执行的函数,并返回message的内容作为错误信息(error函数永远都不会返回)。
通常情况下,error会附加一些错误位置的信息到message头部。
Level参数指示获得错误的位置:
- Level=1[默认]:为调用error位置(文件+行号)
- Level=2:指出哪个调用error的函数的函数
- Level=0:不添加错误位置信息
pcall 和 xpcall、debug
Lua中处理错误,可以使用函数pcall(protected call)来包装需要执行的代码。
pcall接收一个函数和要传递给后者的参数,并执行,执行结果:有错误、无错误;返回值true或者或false、errorinfo。
语法格式:
if pcall(function_name,....) then
-- 没有错误
else
-- 一些错误
end
print(pcall(function(i) print(i) end, 33))
print(pcall(function(i) print(i) error('error..') end, 33))
--[[
输出结果:
33
true
33
false LuaStudy.lua:362: error..
--]]
function f() return false,2 end
if f() then print '1' else print '0' end --输出:0
pcall以一种"保护模式"来调用第一个参数,因此pcall可以捕获函数执行中的任何错误。
通常在错误发生时,希望得到更多的调试信息,而不只是发生错误的位置。但pcall返回时,它已经销毁了调用桟的部分内容。
Lua提供了xpcall函数,xpcall接收第二个参数(一个错误处理函数),当错误发生时,Lua会在调用桟展开(unwind)前调用错误处理函数,于是就可以在这个函数中使用debug库来获取关于错误的额外信息了。
debug库提供了两个通用的错误处理函数:
- debug.debug:提供一个Lua提示符,让用户来检查错误的原因。
- debug.traceback:根据调用桟来构建一个扩展的错误消息。
print(xpcall(function(i) print(i) error('error..') end, function() print(debug.traceback()) end, 33))
--[[
输出结果:
nil
stack traceback:
LuaStudy.lua:367: in function <LuaStudy.lua:367>
[C]: in function 'error'
LuaStudy.lua:367: in function <LuaStudy.lua:367>
[C]: in function 'xpcall'
LuaStudy.lua:367: in main chunk
[C]: ?
false nil
--]]
function myfunction()
n = n/nil
end
function myerrorhandler(err)
print( "ERROR:", err )
end
status = xpcall(myfunction, myerrorhandler)
print(status)
--[[
输出结果:
ERROR: LuaStudy.lua:370: attempt to perform arithmetic on global 'n' (a nil value)
false
--]]
Lua调试(Debug)
Lua提供了debug库用于提供创建我们自定义调试器的功能。Lua本身并未有内置的调试器,但很多开发者共享了他们的Lua调试器代码。
Lua中debug库包含以下函数:
序号 | 方法 & 用途 |
---|---|
1 | debug(): 进入一个用户交互模式,运行用户输入的每个字符串。 使用简单的命令以及其它调试设置,用户可以检阅全局变量和局部变量, 改变变量的值,计算一些表达式,等等。 |
2 | getfenv(object): 返回对象的环境变量。 |
3 | gethook(optional thread): 返回三个表示线程钩子设置的值: 当前钩子函数,当前钩子掩码,当前钩子计数 |
4 | getinfo([thread,] f [, what]): 返回关于一个函数信息的表。 你可以直接提供该函数, 也可以用一个数字f表示该函数。 数字f表示运行在指定线程的调用栈对应层次上的函数: 0层表示当前函数(getinfo 自身); 1 层表示调用getinfo的函数(除非是尾调用,这种情况不计入栈);等等。 如果f是一个比活动函数数量还大的数字,getinfo返回nil。 |
5 | debug.getlocal([thread,] f, local): 此函数返回在栈的f层处函数的索引为local的局部变量的名字和值。 这个函数不仅用于访问显式定义的局部变量,也包括形参、临时变量等。 |
6 | getmetatable(value): 把给定索引指向的值的元表压入堆栈。如果索引无效,或是这个值没有元表,函数将返回0并且不会向栈上压任何东西。 |
7 | getregistry(): 返回注册表,这是一个预定义出来的表, 可以用来保存任何C代码想保存的Lua值。 |
8 | getupvalue(f, up) 此函数返回函数f的第up个上值的名字和值。 如果该函数没有那个上值,返回nil 。 |
10 | sethook([thread,] hook, mask [, count]): 将一个函数作为钩子函数设入。 字符串mask以及数字count决定了钩子将在何时调用。 掩码是由下列字符组合成的字符串,每个字符有其含义:
|
11 | setlocal([thread,] level, local, value): 这个函数将value赋给 栈上第level层函数的第local个局部变量。 如果没有那个变量,函数返回nil 。 如果level越界,抛出一个错误。 |
12 | setmetatable(value, table): 将value的元表设为table(可以是 nil)。 返回 value。 |
13 | setupvalue(f, up, value): 这个函数将value设为函数f的第up个上值。 如果函数没有那个上值,返回nil否则,返回该上值的名字。 |
14 | traceback([thread,] [message [, level]]): 如果message有,且不是字符串或nil, 函数不做任何处理直接返回message。 否则,它返回调用栈的栈回溯信息。 字符串可选项message被添加在栈回溯信息的开头。 数字可选项level指明从栈的哪一层开始回溯 (默认为 1 ,即调用traceback的那里)。 |
function myfunction()
print(debug.traceback("Stack trace"))
print(debug.getinfo(1))
print("Stack trace end")
return 10
end
myfunction()
print(debug.getinfo(1))
--[[
输出结果:
Stack trace
stack traceback:
LuaStudy.lua:381: in function 'myfunction'
LuaStudy.lua:386: in main chunk
[C]: ?
table: 00BF9820
Stack trace end
table: 00BF9A50
--]]
在以实例中,我们使用到了debug库的traceback和getinfo函数,getinfo函数用于返回函数信息的表。
我们经常需要调试函数的内的局部变量。我们可以使用setupvalue函数来设置这些局部变量。实例如下:
function newCounter ()
local n = 0
local k = 0
return function ()
k = n
n = n + 1
return n
end
end
counter = newCounter ()
print(counter())
print(counter())
local i = 1
repeat
name, val = debug.getupvalue(counter, i)
if name then
print ("index", i, name, "=", val)
if(name == "n") then
debug.setupvalue (counter,2,10)
end
i = i + 1
end -- if
until not name
print(counter())
--[[
输出结果:
1
2
index 1 k = 1
index 2 n = 2
11
--]]
在以上实例中,计数器在每次调用时都会自增1。实例中我们使用了getupvalue函数查看局部变量的当前状态。我们可以设置局部变量为新值。实例中,在设置前n的值为2,使用setupvalue函数将其设置为10。现在我们调用函数,执行后输出为11而不是3。
调试类型
- 命令行调试
- 图形界面调试
命令行调试器有:RemDebug、clidebugger、ctrace、xdbLua、LuaInterface - Debugger、Rldb、ModDebug。
图形界调试器有:SciTE、Decoda、ZeroBrane Studio、akdebugger、luaedit。
Lua垃圾回收
Lua采用了自动内存管理。 这意味着你不用操心新创建的对象需要的内存如何分配出来, 也不用考虑在对象不再被使用后怎样释放它们所占用的内存。
Lua运行了一个垃圾收集器来收集所有死对象 (即在Lua中不可能再访问到的对象)来完成自动内存管理的工作。 Lua中所有用到的内存,如:字符串、表、用户数据、函数、线程、 内部结构等,都服从自动管理。
Lua实现了一个增量标记-扫描收集器。它使用这两个数字来控制垃圾收集循环:垃圾收集器间歇率和垃圾收集器步进倍率。这两个数字都使用百分数为单位 (例如:值100在内部表示1)。
垃圾收集器间歇率控制着收集器需要在开启新的循环前要等待多久。增大这个值会减少收集器的积极性。 当这个值比100小的时候,收集器在开启新的循环前不会有等待。设置这个值为200就会让收集器等到总内存使用量达到之前的两倍时才开始新的循环。
垃圾收集器步进倍率控制着收集器运作速度相对于内存分配速度的倍率。增大这个值不仅会让收集器更加积极,还会增加每个增量步骤的长度。不要把这个值设得小于100 ,那样的话收集器就工作的太慢了以至于永远都干不完一个循环。默认值是200,这表示收集器以内存分配的"两倍"速工作。
如果你把步进倍率设为一个非常大的数字 (比你的程序可能用到的字节数还大 10% ),收集器的行为就像一个stop-the-world收集器。接着你若把间歇率设为200 ,收集器的行为就和过去的Lua版本一样了: 每次Lua使用的内存翻倍时,就做一次完整的收集。
垃圾回收器函数
Lua提供了以下函数collectgarbage ([opt [, arg]])用来控制自动内存管理:
-
collectgarbage("collect"): 做一次完整的垃圾收集循环。通过参数opt它提供了一组不同的功能。
-
collectgarbage("count"): 以K字节数为单位返回Lua使用的总内存数。这个值有小数部分,所以只需要乘上 1024 就能得到Lua使用的准确字节数(除非溢出)。
-
collectgarbage("restart"): 重启垃圾收集器的自动运行。
-
collectgarbage("setpause"): 将arg设为收集器的间歇率。 返回间歇率的前一个值。
-
collectgarbage("setstepmul"): 返回步进倍率的前一个值。
-
collectgarbage("step"): 单步运行垃圾收集器。 步长"大小"由arg控制。传入0时,收集器步进(不可分割的)一步。传入非0值,收集器收集相当于Lua分配这些多(K字节)内存的工作。 如果收集器结束一个循环将返回true。
-
collectgarbage("stop"): 停止垃圾收集器的运行。在调用重启前,收集器只会因显式的调用运行。
mytable = {"apple", "orange", "banana"}
print(collectgarbage("count"))
mytable = nil
print(collectgarbage("count"))
print(collectgarbage("collect"))
print(collectgarbage("count"))
--[[
输出结果:
21.0380859375
21.0673828125
0
19.4814453125
--]]
Lua面向对象
面向对象编程(Object Oriented Programming,OOP)是一种非常流行的计算机编程架构。
以下几种编程语言都支持面向对象编程:
- C++
- Java
- Objective-C
- Smalltalk
- C#
- Ruby
面向对象的特征
- 封装:指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。
- 继承:继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。
- 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
- 抽象:抽象(Abstraction)是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在最恰当的继承级别解释问题。
Lua中的面向对象
我们知道,对象由属性和方法组成。Lua中最基本的结构是table,所以需要用table来描述对象的属性。
lua中的function可以用来表示方法。那么Lua中的类可以通过table + function模拟出来。
至于继承,可以通过metetable模拟出来(不推荐用,只模拟最基本的对象大部分实现够用了)。
Lua中的表不仅在某种意义上是一种对象。像对象一样,表也有状态(成员变量);也有与对象的值独立的本性,特别是拥有两个不同值的对象(table)代表两个不同的对象;一个对象在不同的时候也可以有不同的值,但他始终是一个对象;与对象类似,表的生命周期与其由什么创建、在哪创建没有关系。对象有他们的成员函数,表也有:
Account = {balance = 0}
function Account.withdraw (v)
Account.balance = Account.balance - v
end
这个定义创建了一个新的函数,并且保存在Account对象的withdraw域内,我们可以这样调用:
Account.withdraw(100)
简单实例
以下简单的类包含了三个属性: area、length和breadth,printArea方法用于打印计算结果:
Rectangle = {area = 0,length = 0,width = 0} --元类
function Rectangle:new(o,length,width)
o = o or {}
setmetatable(o,self)
self.__index = self
self.length = length or 0
self.width = width or 0
self.area = length * width
return o
end
function Rectangle:printArea()
print("矩形的面积 = "..self.area)
end
--创建对象:创建对象是为类的实例分配内存的过程。每个类都有属于自己的内存并共享公共数据。
obj = Rectangle:new(nil,10,20)
print(obj.length) --访问成员属性
obj:printArea() --访问成员函数
--内存在对象初始化时分配。
--[[
输出结果:
10
矩形的面积 = 200
--]]
完整实例
Shape = {area = 0} --元类
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
function Shape:printArea()
print("面积 = "..self.area)
end
obj = Shape:new(nil,10) --创建对象
obj:printArea() --访问成员函数
--输出:面积 = 100
Lua继承
继承是指一个对象直接使用另一对象的属性和方法。可用于扩展基础类的属性和方法。
以下演示了一个完整的继承实例:
Shape = {area = 0} --基础类
function Shape:new(o,side) --基础类new方法
o = o or {}
setmetatable(o,self)
self.__index = self
side = side or 0
self.area = side * side
return o
end
function Shape:printArea() --基础类printArea方法
print("面积 = "..self.area)
end
shape = Shape:new(nil,10) --实例化基础类Shape对象
shape:printArea() --访问基础类Shape的printArea方法
Square = Shape:new() --Square对象继承了Shape类
function Square:new(o,side) --派生类new方法
o = o or Shape:new(o,side)
setmetatable(o,self)
self.__index = self
return o
end
function Square:printArea() --派生类printArea方法
print("正方形面积 = "..self.area)
end
square = Square:new(nil,20) --实例化派生类Square对象
square:printArea() --访问派生类Square的printArea方法
Rectangle = Shape:new() --Rectangle对象继承了Shape类
function Rectangle:new(o,length,width) --派生类new方法
o = o or Shape:new(o)
setmetatable(o,self)
self.__index = self
self.area = length * width
return o
end
function Rectangle:printArea() --派生类printArea方法
print("矩形面积 = "..self.area)
end
rectangle = Rectangle:new(nil,30,30) --实例化派生类Rectangle对象
rectangle:printArea() --访问派生类Rectangle的printArea方法
--[[
输出结果:
面积 = 100
正方形面积 = 400
矩形面积 = 900
--]]
函数重写
function Square:printArea() --重写
print("正方形面积 = "..self.area)
end
function Rectangle:printArea() --重写
print("矩形面积 = "..self.area)
end