# Lua 入门学习
# 🍁Lua 基本语法
# 交互式编程
Lua 提供了人交互式编程模式。可以在命令行中输出程序并立即查看效果
Lua 交互式编程通过命令 lua -i 或 lua 来启用:
$ lua -i | |
$ lua Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio | |
> |
在命令行中,输入以下命令:
> print("Hello world!") |
按下回车,得到以下结果:
> print("Hello World!") | |
Hello World! | |
> |
# 脚本编程
我们可以将 Lua 程序代码保持到一个以 lua 结尾的文件,并执行,该模式称为脚本式编程,如我们将如下代码存储在名为 hello.lua 的脚本文件中:
print("Hello World!") |
使用 lua 名执行以上脚本,输出结果为:
$ lua test.lua | |
Hello World! |
我们也可以将代码修改为如下形式来执行脚本(在开头添加:#!/usr/local/bin/lua):
#!/usr/local/bin/lua | |
print("Hello World!") |
以上代码中,我们指定了 Lua 的解释器 /usr/local/bin directory。加上 # 号标记解释器会忽略它。接下来我们为脚本添加可执行权限,并执行:
./test.lua | |
Hello World! |
# 注释
# 单行注释
两个减号是单行注释
-- |
# 多行注释
--[[
多行注释
多行注释
--]]
# 标示符
Lua 标示符用于定义一个变量,函数获取其他用户定义的项。标示符以一个字母 A 到 Z 或 a 到 z 或下划线 _ 开头后加上 0 个或多个字母,下划线,数字(0 到 9)。最好不要使用下划线加大写字母的标示符,因为 Lua 的保留字也是这样的。
Lua 不允许使用特殊字符如 @, $, 和 % 来定义标示符。 Lua 是一个区分大小写的编程语言。因此在 Lua 中 Test 与 test 是两个不同的标示符。以下列出了一些正确的标示符:
mohd zara abc move_name a_123 | |
myname50 _temp j a23b9 retVal |
# 关键词
以下列出了 Lua 的保留关键字。保留关键字不能作为常量或变量或其他用户自定义标示符:
一般约定,以下划线开头连接一串大写字母的名字(比如 _VERSION)被保留用于 Lua 内部全局变量。
# 全局变量
在默认情况下,变量总是认为是全局的。全局变量不需要声明,给一个变量赋值后即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil。
> print(b) | |
nil | |
> b=10 | |
> print(b) | |
10 | |
> |
如果你想删除一个全局变量,只需要将变量赋值为 nil。
b = nil | |
print(b) --> nil |
这样变量 b 就好像从没被使用过一样。换句话说,当且仅当一个变量不等于 nil 时,这个变量即存在。
# 🍁Lua 数据类型
Lua 是动态数据类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。
Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。
我们可以使用 type 函数测试给定变量或者值的类型:
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 表示一个没有任何有效值,它只有一个值 --nil,例如打印一个没有赋值的变量,就会输出一个 nil 值:
print(type(a)) |
结果为:
nil |
对于全局变量和 table,nil 还有一个 “删除” 作用,给全局变量或者 table 表里的变量赋一个 nil 值,等同于把它们删掉。
tab1 = { key1 = "val1", key2 = "val2", "val3" } | |
for k, v in pairs(tab1) do | |
print(k .. " - " .. v) | |
end | |
print("\n") | |
tab1.key1 = nil | |
for k, v in pairs(tab1) do | |
print(k .. " - " .. v) | |
end |
结果为:
1-val3 | |
key1-val1 | |
key2-val2 | |
1-val3 | |
key2-val2 |
nil 作比较时应该加上双引号 ":
type(x) | |
type(x)==nil | |
type(x)=="nil" |
结果为:
nil | |
false | |
true |
# boolean
boolean 类型只有两个可选值:true(真) 和 false(假),Lua 把 false 和 nil 看作是 "假",其他的都为 "真":
print(type(true)) | |
print(type(false)) | |
print(type(nil)) | |
if false or nil then | |
print("至少有一个是true") | |
else | |
print("false和nil都为false") | |
end |
执行结果为:
boolean | |
boolean | |
nil | |
false和nil都为false |
# number (数字)
Lua 默认只有一种 number 类型 -- double(双精度)类型(默认类型可以修改 luaconf.h 里的定义),以下几种写法都被看作是 number 类型:
print(type(2)) | |
print(type(2.2)) | |
print(type(0.2)) | |
print(type(2e+1)) | |
print(type(0.2e-1)) | |
print(type(7.8263692594256e-06)) |
执行结果为:
number | |
number | |
number | |
number | |
number | |
number |
# string (字符串)
字符串由一对双引号或单引号来表示。
string1 = "this is string1" | |
string2 = 'this is string2' |
也可以用 2 个方括号 "[[]]" 来表示 "一块" 字符串
html = [[ | |
<html> | |
<head></head> | |
<body> | |
<a href="http://www.baidu.com/">百度</a> | |
</body> | |
</html> | |
]] | |
print(html) |
执行结果为:
<html> | |
<head></head> | |
<body> | |
<a href="http://www.baidu.com/">百度</a> | |
</body> | |
</html> |
在对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字:
print("2"+6) | |
print("2"+"6") | |
print("2+6") | |
print("-2e2"*"6") |
执行以上代码为:
8 | |
8 | |
2+6 | |
-1200 |
若是执行下面的代码:
print("error" + 1) |
则会报错。Lua 中字符串连接使用的是 ..
print("a" .. 'b') | |
print(157 .. 428) |
执行结果为:
ab | |
157428 |
使用 **#** 来计算字符串的长度,放在字符串的前面:
len = "www.baidu.com" | |
print(#len) | |
print(#"ww.baidu.com") |
执行结果为:
13 | |
13 |
# table (表)
在 Lua 里,table 的创建是通过 "构造表达式" 来完成,最简单构造表达式是 {},用来创建一个空表。也可以在表里添加一些数据,直接初始化表:
-- 创建一个空的 table | |
local tbl1 = {} | |
-- 直接初始表 | |
local tbl2 = {"apple", "pear", "orange", "grape"} |
Lua 中的表(table)其实是一个 "关联数组"(associative arrays),数组的索引可以是数字或者是字符串。
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 |
脚本执行结果为:
key : value | |
10 : 30 |
不同于其他语言的数组把 0 作为数组的初始索引,在 Lua 里表的默认初始索引一般以 1 开始。
local tbl = {"apple", "pear", "orange", "grape"} | |
for key, val in pairs(tbl) do | |
print("Key", key) | |
end |
脚本执行结果为:
Key 1 | |
Key 2 | |
Key 3 | |
Key 4 |
table 不会固定长度大小,有新数据添加时 table 长度会自动增长,没初始的 table 都是 nil。
a3 = {} | |
for i = 1, 10 do | |
a3[i] = i | |
end | |
a3["key"] = "val" | |
print(a3["key"]) | |
print(a3["none"]) |
脚本执行结果:
val | |
nil |
# function (函数)
在 Lua 中,函数是被看作是 "第一类值(First-Class Value)",函数可以存在变量里:
function factorial1(n) | |
if n == 0 then | |
return 1 | |
else | |
return n * factorial1(n - 1) | |
end | |
end | |
print(factorial1(5)) | |
factorial2 = factorial1 | |
print(factorial2(5)) |
脚本执行结果为:
120
120
function 可以以匿名函数(anonymous function)的方式通过参数传递:
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 | |
); |
脚本执行结果为:
key1 = val1 | |
key2 = val2 |
# thread (线程)
在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。
# userdata (自定义类型)
userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。
# 🍁Lua 变量
变量在使用前,必须在代码中进行声明,即创建该变量。
编译程序执行代码之前编译器需要知道如何给语句变量开辟存储区,用于存储变量的值。
Lua 变量有三种类型:全局变量、局部变量、表中的域。
Lua 中的变量全是全局变量,那怕是语句块或是函数里,除非用 local 显式声明为局部变量。
局部变量的作用域为从声明位置开始到所在语句块结束。
变量的默认值均为 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 |
执行以上实例输出结果为:
5 nil | |
6 6 | |
5 6 |
# 赋值语句
赋值是改变一个变量的值和改变表域的最基本的方法。
a = "hello" .. "world" | |
t.n = t.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 -- 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() |
f () 返回两个值,第一个赋给 a,第二个赋给 b。
应该尽可能的使用局部变量,有两个好处:1. 避免命名冲突。2. 访问局部变量的速度比全局变量更快。
# 索引
对 table 的索引使用方括号 [];Lua 也提供了。操作。
t[i] | |
t.i -- 当索引为字符串类型时的一种简化写法 | |
gettable_event(t,i) -- 采用索引访问本质上是一个类似这样的函数调用 |
例如:
site = {} | |
site["key"] = "www.baidu.cc" | |
print(site["key"]) | |
print(site.key) |
结果为:
www.baidu.cc | |
www.baidu.cc |
# 🍁Lua 循环
很多情况下我们需要做一些有规律性的重复操作,因此在程序中就需要重复执行某些语句。
一组被重复执行的语句称之为循环体,能否继续重复,决定循环的终止条件。
循环结构是在一定条件下反复执行某段程序的流程结构,被反复执行的程序被称为循环体。
循环语句是由循环体及循环的终止条件两部分组成的。
Lua 语言提供了以下几种循环处理方式:
循环控制语句
循环控制语句用于控制程序的流程, 以实现程序的各种结构方式。
Lua 支持以下循环控制语句:
无限循环
在循环体中如果条件永远为 true 循环语句就会永远执行下去,以下以 while 循环为例:
while( true ) | |
do | |
print("循环将永远执行下去") | |
end |
# while 循环
# 语法
Lua 编程语言中 while 循环语法:
while(condition) | |
do | |
statements | |
end |
statements (循环体语句) 可以是一条或多条语句,condition (条件) 可以是任意表达式,在 condition (条件) 为 true 时执行循环体语句。 流程图如下:
在以上流程图中我们可以看出在 condition (条件) 为 false 时会跳过当前循环并开始脚本执行紧接着的语句。
# 示例
以下实例循环输出 a 的值:
a=10 | |
while( a < 20 ) | |
do | |
print("a 的值为:", a) | |
a = a+1 | |
end |
执行以上代码,输出结果如下:
a 的值为: 10
a 的值为: 11
a 的值为: 12
a 的值为: 13
a 的值为: 14
a 的值为: 15
a 的值为: 16
a 的值为: 17
a 的值为: 18
a 的值为: 19
# for 循环
Lua 编程语言中 for 循环语句可以重复执行指定语句,重复次数可在 for 语句中控制。
Lua 编程语言中 for 语句有两大类::
数值 for 循环
泛型 for 循环
# 数值 for 循环
for var=exp1,exp2,exp3 do | |
<执行体> | |
end |
var 从 exp1 变化到 exp2,每次变化以 exp3 为步长递增 var,并执行一次 "执行体"。exp3 是可选的,如果不指定,默认为 1。
var
首先执行,执行一次。此步骤允许声明和初始化任何循环控制变量- 接下来执行
exp2
。 这是循环继续执行的最大值或最小值。 它在内部创建条件检查,以比较初始值和最大 / 最小值。 - 在执行
for
循环体之后,控制流会跳回到exp3
语句。此语句用于更新循环控制变量。 - 再次评估条件。 如果为真,则循环执行并且过程自身重复 (循环体,然后递增步骤,然后再次调节)。 条件变为
false
后,for
循环终止。
# 实例
for i=1,f(x) do | |
print(i) | |
end | |
for i=10,1,-1 do | |
print(i) | |
end |
for 的三个表达式在循环开始前一次性求值,以后不再进行求值。比如上面的 f (x) 只会在循环开始前执行一次,其结果用在后面的循环中。
验证如下:
function f(x) | |
print("function") | |
return x*2 | |
end | |
for i=1,f(5) do print(i) | |
end |
以上实例输出结果为:
function | |
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 |
# 泛型 for 循环
泛型 for 循环通过一个迭代器函数来遍历所有值,类似 java 中的 foreach 语句。Lua 编程语言中泛型 for 循环语法格式:
-- 打印数组 a 的所有值 | |
for i,v in ipairs(a) | |
do print(v) | |
end |
i 是数组索引值,v 是对应索引的数组元素值。ipairs 是 Lua 提供的一个迭代器函数,用来迭代数组。
# 实例
days = {"Suanday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"} | |
for i,v in ipairs(days) | |
do | |
print(v) | |
end |
以上实例输出结果为:
Suanday | |
Monday | |
Tuesday | |
Wednesday | |
Thursday | |
Friday | |
Saturday |
# 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 循环流程图如下:
# 实例
--[变量定义 --] | |
a = 10 | |
--[执行循环 --] | |
repeat | |
print("a的值为:", a) | |
a = a + 1 | |
until( a > 15 ) |
执行以上代码,程序输出结果为:
a的值为: 10 | |
a的值为: 11 | |
a的值为: 12 | |
a的值为: 13 | |
a的值为: 14 | |
a的值为: 15 |
# 嵌套循环
# 语法
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 ) |
# 实例
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 |
# break 语句
Lua 编程语言 break 语句插入在循环体中,用于退出当前循环或语句,并开始脚本执行紧接着的语句。
如果你使用循环嵌套,break 语句将停止最内层循环的执行,并开始执行的外层的循环语句。
# 语法
break |
流程图:
# 实例
下实例执行 while 循环,在变量 a 小于 20 时输出 a 的值,并在 a 大于 15 时终止执行循环:
--[定义变量 --] | |
a = 10 | |
--[while 循环 --] | |
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 流程控制
Lua 编程语言流程控制语句通过程序设定一个或多个条件语句来设定。在条件为 true 时执行指定程序代码,在条件为 false 时执行其他指定代码。
下图是典型的流程控制流程图:
控制结构的条件表达式结果可以是任何值,Lua 认为 false 和 nil 为假,true 和非 nil 为真。
要注意的是 Lua 中 0 为 true:
--[0 为 true] | |
if(0) | |
then | |
print("0 为 true") | |
end |
以上代码输出结果为:
0 为 true |
Lua 提供了以下控制结构语句:
# if 语句
if(布尔表达式) | |
then | |
--[在布尔表达式为 true 时执行的语句 --] | |
end |
在布尔表达式为 true 时会 if 中的代码块会被执行,在布尔表达式为 false 时,紧跟在 if 语句 end 之后的代码会被执行。Lua 认为 false 和 nil 为假,true 和非 nil 为真。要注意的是 Lua 中 0 为 true。
if 语句流程图如下:
# 实例
--[定义变量 --] | |
a = 10; | |
--[使用 if 语句 --] | |
if( a < 20 ) | |
then | |
--[if 条件为 true 时打印以下信息 --] | |
print("a 小于 20" ); | |
end | |
print("a 的值为:", a); |
执行结果为:
a 小于 20 | |
a 的值为: 10 |
# if...else 语句
if(布尔表达式) | |
then | |
--[布尔表达式为 true 时执行该语句块 --] | |
else | |
--[布尔表达式为 false 时执行该语句块 --] | |
end |
在布尔表达式为 true 时会 if 中的代码块会被执行,在布尔表达式为 false 时,else 的代码块会被执行。Lua 认为 false 和 nil 为假,true 和非 nil 为真。要注意的是 Lua 中 0 为 true。
if 语句流程图如下:
# 实例
--[定义变量 --] | |
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...else if...else 语句
if( 布尔表达式 1) | |
then | |
--[在布尔表达式 1 为 true 时执行该语句块 --] | |
elseif( 布尔表达式 2) | |
then | |
--[在布尔表达式 2 为 true 时执行该语句块 --] | |
elseif( 布尔表达式 3) | |
then | |
--[在布尔表达式 3 为 true 时执行该语句块 --] | |
else | |
--[如果以上布尔表达式都不为 true 则执行该语句块 --] | |
end |
# 实例
--[定义变量 --] | |
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 |
# if 嵌套语句
if( 布尔表达式 1) | |
then | |
--[布尔表达式 1 为 true 时执行该语句块 --] | |
if(布尔表达式 2) | |
then | |
--[布尔表达式 2 为 true 时执行该语句块 --] | |
end | |
end |
# 实例
--[定义变量 --] | |
a = 100; | |
b = 200; | |
--[检查条件 --] | |
if( a == 100 ) | |
then | |
--[if 条件为 true 时执行以下 if 条件判断 --] | |
if( b == 200 ) | |
then | |
--[if 条件为 true 时执行该语句块 --] | |
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. 计算并返回值,这种情况下函数作为赋值语句的表达式使用。
# 函数定义
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 语言函数可以返回多个值,每个值以逗号隔开。
# 实例
--[[函数返回两个值的最大值 --]] | |
function max(num1, num2) | |
if (num1 > num2) then | |
result = num1; | |
else | |
result = num2; | |
end | |
return result; | |
end | |
-- 调用函数 | |
print("两值比较最大值为 ",max(10,4)) | |
print("两值比较最大值为 ",max(5,6)) |
以上代码执行结果为:
两值比较最大值为 10 | |
两值比较最大值为 6 |
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("www.baidu.com", "baidu") | |
print(s, e) |
结果为:
5 9 |
Lua 函数中,在 return 后列出要返回的值得列表即可返回多值,如
function maximum (a) | |
local mi = 1 -- 最大值索引 | |
local m = a[mi] -- 最大值 | |
for i,val in ipairs(a) do | |
if val > m then | |
mi = i | |
m = val | |
end | |
end | |
return m, mi | |
end | |
print(maximum({8,10,23,12,5})) |
以上代码执行结果为:
23 3 |
# 可变参数
Lua 函数可以接受可变数目的参数,和 C 语言类似,在函数参数列表中使用三点 ... 表示函数有可变的参数。
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))25 |
我们也可以通过 select ("#",...) 来获取可变参数的数量:
function average(...) | |
result = 0 | |
local arg={...} | |
for i,v in ipairs(arg) do | |
result = result + v | |
end | |
print("总共传入 " .. select("#",...) .. " 个数") | |
return result/select("#",...) | |
end | |
print("平均值为",average(10,5,3,4,5,6)) |
有时候我们可能需要几个固定参数加上可变参数,固定参数必须放在变长参数之前:
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 和 1 |
通常在遍历变长参数的时候只需要使用 {…},然而变长参数可能会包含一些 nil,那么就可以用 select 函数来访问变长参数了:select ('#', …) 或者 select (n, …)
select ('#', …) 返回可变参数的长度
select (n, …) 用于访问 n 到 select ('#',…) 的参数
调用 select 时,必须传入一个固定实参 selector (选择开关) 和一系列变长参数。如果 selector 为数字 n, 那么 select 返回它的第 n 个可变实参,否则只能为字符串 "#", 这样 select 会返回变长参数的总数。例子代码:
do | |
function foo(...) | |
for i = 1, select('#', ...) do --> 获取参数总数 | |
local arg = select(i, ...); --> 读取参数 | |
print("arg", arg); | |
end | |
end | |
foo(1, 2, 3, 4); | |
end |
输出结果:
arg 1 | |
arg 2 | |
arg 3 | |
arg 4 |
# 🍁Lua 运算符
运算符是一个特殊的符号,用于告诉解释器执行特定的数学或逻辑运算。Lua 提供了以下几种运算符类型:
算术运算符
关系运算符
逻辑运算符
其他运算符
# 算数运算符
下表列出了 Lua 语言中的常用算术运算符,设定 A 的值为 10,B 的值为 20:
# 关系运算符
下表列出了 Lua 语言中的常用关系运算符,设定 A 的值为 10,B 的值为 20:
# 逻辑运算符
下表列出了 Lua 语言中的常用逻辑运算符,设定 A 的值为 true,B 的值为 false:
# 其他运算符
下表列出了 Lua 语言中的连接运算符与计算表或字符串长度的运算符:
# 运算符优先级
从高到低的顺序:
^ | |
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 语言中字符串可以使用以下三种方式来表示:
单引号间的一串字符
双引号的一串字符
[[和]] 间的一串字符
以上三种方式的字符串实例如下:
string1 = "Lua" | |
print("\"字符串 1 是\"",string1) | |
string2 = 'runoob.com' | |
print("字符串 2 是",string2) | |
string3 = [["Lua 教程"]] | |
print("字符串 3 是",string3) |
以上代码执行输出结果是:
"字符串 1 是" Lua | |
字符串 2 是 runoob.com | |
字符串 3 是 "Lua 教程" |
转义字符用于表示不能直接显示的字符,比如后退键,回车键,等。如在字符串转换双引号可以使用 """。所有的转义字符和所对应的意义:
# 字符串操作
# 字符串格式化
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 位.
# 🍁Lua 数组
数组,就是相同数据类型的元素按一定顺序排列的集合,可以是一维数组和多维数组。Lua 数组的索引键值可以使用整数表示,数组的大小不是固定的。
# 一维数组
一维数组是最简单的数组,其逻辑结构是线性表。一维数组可以用 for 循环出数组中的元素,如下实例:
array={"Lua","Tutorial"} | |
for i=0,2 do | |
print(array[i]) | |
end |
以上代码执行输出结果为:
nil | |
Lua | |
Tutorial |
正如你所看到的,我们可以使用整数索引来访问数组元素,如果知道的索引没有值则返回 nil。在 Lua 索引值是以 1 为起始,但你也可以指定 0 开始。除此外我们还可以以负数为数组索引值:
array = {} | |
for i= -2, 2 do | |
array[i] = i *2 | |
end | |
for i = -2,2 do | |
print(array[i]) | |
end |
执行结果:
-4 | |
-2 | |
0 | |
2 | |
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 |
以上代码执行输出结果为:
1 | |
2 | |
3 | |
2 | |
4 | |
6 | |
3 | |
6 | |
9 |
如你所看到的,以上的实例中,数组设定了指定的索引值,这样可以避免出现 nil 值,有利于节省内存空间。
# 🍁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 设为收集器的 间歇率 (参见 §2.5)。 返回 间歇率 的前一个值。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")) |
执行以上程序,输出结果如下 (注意内存使用的变化):
20.9560546875 | |
20.9853515625 | |
0 | |
19.4111328125 |
# 🍁Lua 数据库访问
Lua 数据库的操作库为:LuaSQL。它是开源的,支持的数据库有:ODBC、ADO、Oracle、MySQL、SQLite 和 PostgreSQL。
LuaSQL 可以使用 LuaRocks 来安装可以根据需要安装你需要的数据库驱动。LuaRocks 安装方法:
$ wget http://luarocks.org/releases/luarocks-2.2.1.tar.gz | |
$ tar zxpf luarocks-2.2.1.tar.gz | |
$ cd luarocks-2.2.1 | |
$ ./configure; sudo make bootstrap | |
$ sudo luarocks install luasocket | |
$ lua | |
Lua 5.3.0 Copyright (C) 1994-2015 Lua.org, PUC-Rio | |
> require "socket" |
Window 下安装 LuaRocks:https://github.com/keplerproject/luarocks/wiki/Installation-instructions-for-Windows
安装不同数据库驱动:
luarocks install luasql-sqlite3 | |
luarocks install luasql-postgres | |
luarocks install luasql-mysql | |
luarocks install luasql-sqlite | |
luarocks install luasql-odbc |
你也可以使用源码安装方式,Lua Github 源码地址:https://github.com/keplerproject/luasql
Lua 连接 MySql 数据库:
require "luasql.mysql" | |
-- 创建环境对象 | |
env = luasql.mysql() | |
-- 连接数据库 | |
conn = env:connect("数据库名","用户名","密码","IP地址",端口) | |
-- 设置数据库的编码格式 | |
conn:execute"SET NAMES UTF8" | |
-- 执行数据库操作 | |
cur = conn:execute("select * from role") | |
row = cur:fetch({},"a") | |
-- 文件对象的创建 | |
file = io.open("role.txt","w+"); | |
while row do | |
var = string.format("%d %s\n", row.id, row.name) | |
print(var) | |
file:write(var) | |
row = cur:fetch(row,"a") | |
end | |
file:close() -- 关闭文件对象 | |
conn:close() -- 关闭数据库连接 | |
env:close() -- 关闭数据库环境 |
# 🍁Lua 迭代器
迭代器 (iterator) 是一种对象,它能够用来遍历标准模版库容器的部分或全部元素,每个迭代器对象代表容器中的确定的地址。
在 Lua 中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。
# 泛型 for 迭代器
泛型 for 迭代器提供了集合的 key/value 对,语法格式如下:
for k,v in pairs(t) do | |
print(k,v) | |
end |
上面代码中,k 和 v 为变量列表;pairs (t) 为表达式列表。查看如下实例:
array = {"Lua", "Tutorial"} | |
for key,value in ipairs(array) | |
do | |
print(key, value) | |
end |
以上代码执行输出结果为:
1 Lua | |
2 Tutorial |
以上实例中我们使用了 Lua 默认提供的迭代函数 ipairs。
下面我们看看泛型 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 |
以上实例输出结果:
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 |
# 多状态的迭代器
很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到 table 内,将 table 作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在 table 内,所以迭代函数通常不需要第二个参数。
以下实例我们创建了自己的迭代器:
array = {"Lua", "Tutorial"} | |
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 |
以上实例输出结果为:
Lua | |
Tutorial |
以上实例中我们可以看到,elementIterator 内使用了闭包函数,实现计算集合大小并输出各个元素。
# 🍁Lua table (表)
table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数字、字典等。
Lua table 使用关联型数组,你可以使用任意类型的值来作数组的索引,但这个值不能是 nil。
Lua table 是不固定大小的,你可以根据自己需要进行扩容。
Lua 也是通过 table 来解决模块 (module)、包 (package) 和对象 (Object) 的
# table (表) 的构造
构造器是创建和初始化表的表达式。表是 Lua 特有的功能强大的东西。最简单的构造函数是 {},用来创建一个空表。可以直接初始化数组。
-- 初始化表 | |
mytable = {} | |
-- 指定值 | |
mytable[1] = "Lua" | |
-- 移除引用 | |
mytable = nil |
当我们为 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 操作常用的方法:
# Table 连接
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 |
# 插入和移除
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 排序
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 最大值
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 |
# Table 去重
function table.unique(t, bArray) | |
local check = {} | |
local n = {} | |
local idx = 1 | |
for k, v in pairs(t) do | |
if not check[v] then | |
if bArray then | |
n[idx] = v | |
idx = idx + 1 | |
else | |
n[k] = v | |
end | |
check[v] = true | |
end | |
end | |
return n | |
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 的结构,因此可以像操作调用 table 里的元素那样来操作调用模块里的常量或函数。上面的 func2 声明为程序块的局部变量,即表示一个私有函数,因此是不能从外部访问模块里的这个私有函数,必须通过模块里的公有函数来调用。
# require 函数
Lua 提供了一个名为 require 的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:
require("函数名") |
或者
require "<函数名>" |
执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。
-- test_module.lua 文件 | |
-- module 模块为上文提到到 module.lua | |
require("module") | |
print(module.constant) | |
module.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,mymetable) -- 把 mymetatable 设为 mytable 的元表 |
以上代码也可以直接写成一行:
mytable = setmetatable({},{}) |
以下为返回对象元表:
getmetatable(mytable) -- 这会返回 mymetatable |
# __index 元方法
当你通过键来访问 table 的时候,如果这个键没有值,那么 Lua 就会寻找该 table 的 metatable(假定有 metatable)中的 index 键。如果 index 包含一个表格,Lua 会在表格中查找相应的键。
我们可以在使用 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 和键会作为参数传递给函数。**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 个步骤:
在表中查找,如果找到,返回该元素,找不到则继续
判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
判断元表有没有 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 键包含在元表中,并进行相加操作。 表中对应的操作列表如下:(注意:** 是两个下划线)
# __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_test.lua 文件 | |
co = coroutine.create( | |
function(i) | |
print(i); | |
end | |
) | |
coroutine.resume(co, 1) -- 1 | |
print(coroutine.status(co)) -- dead | |
print("----------") | |
co = coroutine.wrap( | |
function(i) | |
print(i); | |
end | |
) | |
co(1) | |
print("----------") | |
co2 = coroutine.create( | |
function() | |
for i=1,10 do | |
print(i) | |
if i == 3 then | |
print(coroutine.status(co2)) --running | |
print(coroutine.running()) --thread:XXXXXX | |
end | |
coroutine.yield() | |
end | |
end | |
) | |
coroutine.resume(co2) --1 | |
coroutine.resume(co2) --2 | |
coroutine.resume(co2) --3 | |
print(coroutine.status(co2)) -- suspended | |
print(coroutine.running()) | |
print("----------") |
以上实例执行输出结果为:
1 | |
dead | |
---------- | |
1 | |
---------- | |
1 | |
2 | |
3 | |
running | |
thread: 0x7fb801c05868 false | |
suspended | |
thread: 0x7fb801c04c88 true | |
---------- |
coroutine.running 就可以看出来,coroutine 在底层实现就是一个线程。
当 create 一个 coroutine 的时候就是在新线程中注册了一个事件。
当使用 resume 触发事件的时候,create 的 coroutine 函数就被执行了,当遇到 yield 的时候就代表挂起当前线程,等候再次 resume 触发事件。
接下来我们分析一个更详细的实例:
function foo (a) | |
print("foo 函数输出", a) | |
return coroutine.yield(2 * a) -- 返回 2*a 的值 | |
end | |
co = coroutine.create(function (a , b) | |
print("第一次协同程序执行输出", a, b) -- co-body 1 10 | |
local r = foo(a + 1) | |
print("第二次协同程序执行输出", r) | |
local r, s = coroutine.yield(a + b, a - b) -- a,b 的值为第一次调用协同程序时传入 | |
print("第三次协同程序执行输出", r, s) | |
return b, "结束协同程序" -- b 的值为第二次调用协同程序时传入 | |
end) | |
print("main", coroutine.resume(co, 1, 10)) -- true, 4 | |
print("--分割线----") | |
print("main", coroutine.resume(co, "r")) -- true 11 -9 | |
print("---分割线---") | |
print("main", coroutine.resume(co, "x", "y")) -- true 10 end | |
print("---分割线---") | |
print("main", coroutine.resume(co, "x", "y")) -- cannot resume dead coroutine | |
print("---分割线---") |
以上实例执行输出结果为:
第一次协同程序执行输出 1 10 | |
foo 函数输出 2 | |
main true 4 | |
-- 分割线 ---- | |
第二次协同程序执行输出 r | |
main true 11 -9 | |
--- 分割线 --- | |
第三次协同程序执行输出 x y | |
main true 10 结束协同程序 | |
--- 分割线 --- | |
main 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 newProductor | |
function productor() | |
local i = 0 | |
while true do | |
i = i + 1 | |
send(i) -- 将生产的物品发送给消费者 | |
end | |
end | |
function consumer() | |
while true do | |
local i = receive() -- 从生产者那里得到物品 | |
print(i) | |
end | |
end | |
function receive() | |
local status, value = coroutine.resume(newProductor) | |
return value | |
end | |
function send(x) | |
coroutine.yield(x) -- x 表示需要发送的值,值返回以后,就挂起该协同程序 | |
end | |
-- 启动程序 | |
newProductor = coroutine.create(productor) | |
consumer() |
以上实例执行输出结果为:
1 | |
2 | |
3 | |
4 | |
5 | |
6 | |
7 | |
8 | |
9 | |
10 | |
11 | |
12 | |
13 | |
…… |
# 🍁Lua 文件 I/O
Lua I/O 库用于读取和处理文件。分为简单模式(和 C 一样)、完全模式
- 简单模式 (simple model) 拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。
- 完全模式 (complete model) 使用外部的文件句柄来实现。它以一种面对对象的形式,将所有文件操作定义位文件句柄的方法。
简单模式在做一些简单的文件操作时较为合适。但是在进行一些高级的文件操作的时候,简单模式就显得力不从心。例如同时读取多个文件这样的操作,使用完全模式则较为合适。
开文件操作如下:
file = io.open(filename[ , mode]) |
mode 的值有:
# 简单模式
简单模式使用标准的 I/O 或使用一个当前输入文件和一个当前输出文件。
以下为 file.lua 文件代码,操作的文件为 test.lua (如果没有你需要创建该文件),代码如下:
-- 以只读方式打开文件 | |
file = io.open("test.lua", "r") | |
-- 设置默认输入文件为 test.lua | |
io.input(file) | |
-- 输出文件第一行 | |
print(io.read()) | |
-- 关闭打开的文件 | |
io.close(file) | |
-- 以附加的方式打开只写文件 | |
file = io.open("test.lua", "a") | |
-- 设置默认输出文件为 test.lua | |
io.output(file) | |
-- 在文件最后一行添加 Lua 注释 | |
io.write("-- test.lua 文件末尾注释") | |
-- 关闭打开的文件 | |
io.close(file) |
执行以上代码,你会发现,输出了 test.ua 文件的第一行信息,并在该文件最后一行添加了 lua 的注释。如我这边输出的是:
-- test.lua 文件 |
在以上实例中我们使用了 io."x" 方法,其中 io.read () 中我们没有带参数,参数可以是下表中的一个:
其他的 io 方法有:
io.tmpfile (): 返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除
io.type (file): 检测 obj 是否一个可用的文件句柄
io.flush (): 向文件写入缓冲中的所有数据
io.lines (optional file name): 返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回 nil, 但不关闭文件
# 完全模式
通常我们需要在同一时间处理多个文件。我们需要使用 file:function_name
来代替 io.function_name
方法。以下实例演示了如同同时处理同一个文件:
-- 以只读方式打开文件 | |
file = io.open("test.lua", "r") | |
-- 输出文件第一行 | |
print(file:read()) | |
-- 关闭打开的文件 | |
file:close() | |
-- 以附加的方式打开只写文件 | |
file = io.open("test.lua", "a") | |
-- 在文件最后一行添加 Lua 注释 | |
file:write("--test") | |
-- 关闭打开的文件 | |
file:close() |
执行以上代码,你会发现,输出了 test.ua 文件的第一行信息,并在该文件最后一行添加了 lua 的注释。如我这边输出的是:
-- test.lua 文件 |
其他方法:
**file:seek(optional whence, optional offset)
😗* 设置和获取当前文件位置,成功则返回最终的文件位置 (按字节), 失败则返回 nil 加错误信息。参数 whence 值可以是:- "set": 从文件头开始
- "cur": 从当前位置开始 [默认]
- "end": 从文件尾开始
- offset: 默认为 0
不带参数 file:seek () 则返回当前位置,file:seek ("set") 则定位到文件头,file:seek ("end") 则定位到文件尾并返回文件大小
file:flush()
: 向文件写入缓冲中的所有数据io.lines(optional file name)
: 打开指定的文件 filename 为读模式并返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回 nil, 并自动关闭文件。若不带参数时 io.lines () <=> io.input ():lines (); 读取默认输入设备的内容,但结束时不关闭文件,如
for line in io.lines("main.lua") do
print(line)
end
以下实例使用了 seek 方法,定位到文件倒数第 25 个位置并使用 read 方法的 *a 参数,即从当期位置 (倒数第 25 个位置) 读取整个文件。
-- 以只读方式打开文件
file = io.open("test.lua", "r")
file:seek("end",-25)
print(file:read("*a"))
-- 关闭打开的文件
file:close()
我这边输出的结果是:
st.lua 文件末尾--test
# 🍁Lua 错误处理
程序运行中错误处理是必要的,在我们进行文件操作,数据转移及 web service 调用过程中都会出现不可预期的错误。如果不注重错误信息的处理,就会造成信息泄露,程序无法运行。
任何程序语言中,都需要错误处理。错误类型有:
- 语法错误
- 运行错误
# 语法错误
语法错误通常是由于对程序的组件 (如运算符、表达式) 使用不当引起的。一个简单的示例如下:
--test.lua 文件 | |
a == 2 |
以上代码执行结果为:
lua: test.lua:2: syntax error near '==' |
正如你看到的,以上出现了两个语法错误,一个 “=” 号跟两个 “=” 号是有区别的。一个 “=” 是赋值表达式,两个 “=” 是比较运算。另一个示例:
for a=1,1,10
print(a)
end
执行以上程序会出现如下错误:
lua: test2.lua:2: 'do' expected near 'print' |
语法错误比程序运行错误更简单,运行错误无法定位具体错误,而语法错误我们可以很快的解决,如以上实例我们只要在 for 语句下添加 do 即可:
for a= 1,10 | |
do | |
print(a) | |
end |
# 运行错误
运行错误是程序可以正常执行,但是会输出报错信息。如下实例由于参数输入错误,程序执行时报错:
function add(a,b) | |
return a+b | |
end | |
add(10) |
当我们编译运行以下代码时,编译是可以成功的,但在运行的时候会产生如下错误
lua: test2.lua:2: attempt to perform arithmetic on local 'b' (a nil value) | |
stack traceback: | |
test2.lua:2: in function 'add' | |
test2.lua:5: in main chunk | |
[C]: ? |
以下报错信息是由于程序缺少 b 参数引起的。b
# 错误处理
我们可以使用两个函数:assert 和 error 来处理错误。实例如下:
local function add(a,b) | |
assert(type(a) == "number","a不是一个数字") | |
assert(type(b) == "number","b不是一个数字") | |
return a+b | |
end | |
add(10) |
执行以上程序会出现如下错误:
lua: test.lua:3: b 不是一个数字 | |
stack traceback: | |
[C]: in function 'assert' | |
test.lua:3: in local 'add' | |
test.lua:6: in main chunk | |
[C]: in ? |
实例中 assert 首先检查第一个参数,若没问题,assert 不做任何事情;否则,assert 以第二个参数作为错误信息抛出。
# error 函数
语法格式
error (message [,level]) |
功能:终止正在执行的函数,并返回 message 的内容作为错误信息 (error 函数永远都不会返回)
通常情况下,error 会附加一下错误位置的信息到 message 头部
level 参数指示获得错误的位置:
- level=1 [默认]:为调用 error 位置 (文件 + 行号)
- rlevel=2:指出哪个调用 error 的函数的函数
- level=0:不添加错误位置信息
# pcall 和 xpacll、debug
Lua 中处理错误,可以使用函数 pcall(protect call)
来包装需要执行的代码。pcall 接受一个函数和要传递给后者的参数,并执行,执行结果:有错误、无错误;返回 true 或者 false,errorinfo。
语法格式如下:
if pcall(function_name,...) then | |
-- 没有错误 | |
else | |
-- 一些错误 | |
end |
简单实例:
> =pcall(function(i) print(i) end, 33) | |
33 | |
true | |
> =pcall(function(i) print(i) error('error..') end, 33) | |
33 | |
false stdin:1: 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:根据调用桟来构建一个扩展的错误消息
xpcall 使用实例 2:
function myfunction () | |
n = n/nil | |
end | |
function myerrorhandler( err ) | |
print( "ERROR:", err ) | |
end | |
status = xpcall( myfunction, myerrorhandler ) | |
print( status) |
执行以上程序会出现如下错误:
ERROR: test2.lua:2: attempt to perform arithmetic on global 'n' (a nil value) | |
false |
# 🍁 Lua 调试 (Debug)
Lua 提供了 debug 库用于提供创建我们自定义调试器的功能。Lua 本身并未有内置的调试器,但很多开发者共享了他们的 Lua 调试代码。
Lua 中 debug 库包含以下函数:
</div>
上表列出了我们常用的调试函数,接下来我们可以看些简单的例子:
function myfunction() | |
print(debug.tracrback("Stack trace")) | |
print(debug.getinfo(1)) | |
print("Stack trace end") | |
return 10 | |
end | |
myfunction() | |
print(debug.getinfo()) |
执行以上代码输出结果为:
Stack trace | |
stack traceback: | |
test2.lua:2: in function 'myfunction' | |
test2.lua:8: in main chunk | |
[C]: ? | |
table: 0054C6C8 | |
Stack trace end |
在以实例中,我们使用到了 debug 库的 traceback 和 getinfo 函数, getinfo 函数用于返回函数信息的表。
另一个实例
我们经常需要调用函数的内部的局部变量。我们 getupvalue 函数来函数来设置这些变量。实例如下:
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 面向对象
# 面向对象的特征
- 封装:指能把一个实体的信息、功能、响应都装入一个单独的对象中的特性
- 继承:继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能得以拓展。这有利于减少重复编码,提高软件的开发效率。
- 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
- 抽象:抽象 (Abstraction) 是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在最恰当的继承级别解释问题。
# Lua 中面向对象
我们知道,对象由属性和方法组成。Lua 中最基本的结构是 table,所以需要用 table 来描述对象的属性。Lua 中的 function 可以用来表示方法。那么 Lua 中的类可以通过 table+function 模拟出来。至于继承可以通过 metatable 模拟出来。
Lua 中的表不仅在某种意义上是一种对象。像对象一样,表也有对象(成员变量);也有与对象的值独立的本性,特别是拥有两个不同值的对象(table)代表两个不同的对象;一个对象在不同的时候也可以拥有的值,但他始终是一个对象;与对象类似,表的生命周期与其由什么创建、在哪创建没有关系。对象有他们的成员函数,表也有:
Account = {balance = 0} | |
function Account.withdraw (v) | |
Account.balance = Account.blance - v | |
end |
这个定义创建了一个新的函数,并且保存在 Account 对象的 withdraw 域内,下面我们可以这样调用:
Account.withdraw(100.00) |
# 一个简单实例
以下简单的类包含了三个属性: area, length 和 breadth,printArea 方法用于打印计算结果:
-- Meta class | |
Rectangle = {area = 0, length = 0, breadth = 0} | |
-- 派生类的方法 new | |
function Rectangle:new (o,length,breadth) | |
o = o or {} | |
setmetatable(o, self) | |
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) |
# 访问属性
我们可以使用点号(.)来访问类的属性
print(r.lenght) |
# 访问成员
我们可以使用冒号:来访问类的成员函数:
r:printArea() |
# 完整实例
-- 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() |
执行以上程序,输出结果为:
面积为 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 |
接下来的实例,Square 对象继承了 Shape 类:
Square = Shape:new() | |
-- Derived class method new | |
function Square:new (o,side) | |
o = o or Shape:new(o,side) | |
setmetatable(o, self) | |
self.__index = self | |
return o | |
end |
# 完整实例
-- 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() |
执行以上代码,输出结果为:
面积为 100 | |
正方形面积为 100 | |
矩形面积为 200 |
# 函数重写
Lua 中我们可以重写基础类的函数,在派生类中定义自己的实现方式:
-- 派生类方法 printArea | |
function Square:printArea () | |
print("正方形面积 ",self.area) | |
end |