准备工作:
创建案例参考:https://blog.csdn.net/u014079773/article/details/101444716
以下所有案例均在该工程中操作
在app目录下新建base.lua,在route.lua文件中添加访问请求
route.lua文件
--[[
这里存储app所有请求url
案例:["/api/test.json"]={"app.test", "test"}
/api/test.json 表示请求url
app.test 请求url的lua文件路径
test 请求url的方法名
例如浏览器访问:
http://localhost/api/test.json
http://localhost/api/lua/test1.json
-- ]]
--定义全局table 名称为route
route = {
["/api/test.json"]={"app.test", "test"},
["/api/lua/test1.json"]={"app.baseLua", "test1"},
["/api/lua/test2.json"]={"app.baseLua", "test2"},
["/api/lua/test3.json"]={"app.baseLua", "test3"},
["/api/lua/test4.json"]={"app.baseLua", "test4"},
["/api/lua/test5.json"]={"app.baseLua", "test5"},
["/api/lua/test6.json"]={"app.baseLua", "test6"},
["/api/lua/test7.json"]={"app.baseLua", "test7"},
["/api/lua/test8.json"]={"app.baseLua", "test8"},
["/api/lua/test9.json"]={"app.baseLua", "test9"}
}
return route
baseLua.lua文件:
--来显示声明一个包
module(..., package.seeall)
function test1()
ngx.say("我们可以使用 type 函数测试给定变量或者值的类型:");
ngx.say(type("Hello world")) --> string
ngx.say(type(10.4*3)) --> number
ngx.say(type(print)) --> function
ngx.say(type(type)) --> function
ngx.say(type(true)) --> boolean
ngx.say(type(nil)) --> nil
ngx.say(type(type(X))) --> string
end
function test2()
ngx.say("#xxx 表示获取字符串长度");
local str=#"helloword";
ngx.say("str:"..str);
end
浏览器访问:http://localhost/api/lua/test1.json
输出结果:
我们可以使用 type 函数测试给定变量或者值的类型:
string
number
function
function
boolean
nil
string
Lua 基本语法:
module(..., package.seeall):来显示声明一个包
-- 表示单行注释
--[[ this is a lua --]] 表示多行注释
ngx.say("输出语句");
print("输出语句");
两者区别:ngx.say自动会换行,而print不会
nil 表示空,删除一个变量则将该变量赋值为nil即可
nil最比较时加双引号如:type(X)=="nil"
变量在使用前,必须在代码中进行声明,即创建该变量。
编译程序执行代码之前编译器需要知道如何给语句变量开辟存储区,用于存储变量的值。
Lua 变量有三种类型:全局变量、局部变量、表中的域。
Lua 中的变量全是全局变量,那怕是语句块或是函数里,除非用 local 显式声明为局部变量。
局部变量的作用域为从声明位置开始到所在语句块结束。
变量的默认值均为 nil。
Lua 可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。
案例:a,b=10,2*x 则表示a=10,b=2*x
a,b,c=10,20 则表示a=10,b=20,c=nil
当变量个数和值的个数不一致时,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
加载模块:require ("模块名") 或 require "模块名"
字符串连接用".."
使用 # 来计算字符串的长度,放在字符串前面如#“helloword” 输出 9
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 函数测试给定变量或者值的类型
boolean 类型只有两个可选值:true(真) 和 false(假),Lua 把 false 和 nil 看作是"假",其他的都为"真":
在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。也可以在表里添加一些数据,直接初始化表:
案例:
-- 创建一个空的 table
local tbl1 = {}
-- 直接初始表
local tbl2 = {"apple", "pear", "orange", "grape"}
Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。
在 Lua 里表的默认初始索引一般以 1 开始。
Lua 循环
Lua 语言提供了以下几种循环处理方式:
循环类型 | 描述 |
---|---|
while 循环 | 在条件为 true 时,让程序重复地执行某些语句。执行语句前会先检查条件是否为 true。 |
for 循环 | 重复执行指定语句,重复次数可在 for 语句中控制。 |
repeat...until | 重复执行循环,直到 指定的条件为真时为止 |
循环嵌套 | 可以在循环内嵌套一个或多个循环语句(while do ... end;for ... do ... end;repeat ... until;) |
Lua 编程语言中 while 循环语法:
while(condition)
do
statements
end
注:Lua中提供了while控制结构,但是没有提供do…while控制结构,同样可以用bread跳出循环。
Lua中不支持continue关键字跳出当前循环进入到下次循环中,所以在写逻辑是要仔细检查避免出错。
Lua 编程语言中 for语句有两大类:数值for,泛型for
Lua 编程语言中数值for循环语法格式:
for var = begin, finish, step do
--body
end
var 从 begin变化到 finish,每次变化以 step 为步长递增 var,并执行一次 "执行体"。finish是可选的,如果不指定,默认为1。
案例:
for i = 1, 5 do
print(i)
end
-- output:
1 2 3 4 5
for i = 1, 10,2 do
print(i)
end
-- output:
1 3 5 7 9
function test12()
local fruits = {"banana","orange","apple" }
for i = 1, #fruits do
ngx.say("key:",i," value:",fruits[i]);
end
end
--输出结果:
key:1 value:banana
key:2 value:orange
key:3 value:apple
Lua 编程语言中泛型 for 循环语法格式:
-- 打印数组a的所有值
local a = {"one", "tow", "three", "four"}
for i, v in ipairs(a) do
print("index:", i, " value:", v)
end
-- output:
index: 1 value: one
index: 2 value: tow
index: 3 value: three
index: 4 value: four
i是数组索引值,v是对应索引的数组元素值。ipairs是Lua提供的一个迭代器函数,用来迭代数组。
Lua 编程语言中 repeat...until 循环语法格式:相当于do...while
repeat
statements
until( condition )
案例:
--[ 变量定义 --]
a = 10
--[ 执行循环 --]
repeat
print("a的值为:", a)
a = a + 1
until( a > 15 )
执行以上代码,程序输出结果为:
a的值为: 10
a的值为: 11
a的值为: 12
a的值为: 13
a的值为: 14
a的值为: 15
循环嵌套:for 循环体中嵌套 while 循环
案例:
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
注:return主要用在函数返回结果,或者结束一个函数的执行,return只能写在语句块的最后,return执行了之后的所有语句都不会执行。
function sum(x ,y)
return x+y
end
local sum = sum(1,1)
print(sum)
Lua 流程控制
Lua 提供了以下控制结构语句:
if 语句 | if 语句 由一个布尔表达式作为条件判断,其后紧跟其他语句组成。 |
if...else 语句 | if 语句 可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码。 |
if 嵌套语句 | 你可以在if 或 else if中使用一个或多个 if 或 else if 语句 。 |
Lua if 语句语法格式如下:
if(布尔表达式) then
--[ 在布尔表达式为 true 时执行的语句 --]
end
案例:
ngx.say("测试if语句");
--[ 定义变量 --]
a = 10;
--[ 使用 if 语句 --]
if(a < 20)
then
--[ if 条件为true时打印以下信息--]
ngx.say("a 小于 20");
end
ngx.say("a 的值为:", a);
以上代码执行结果如下:
a 小于 20
a 的值为: 10
Lua if...else 语句语法格式如下:
if(布尔表达式)
then
--[ 布尔表达式为 true 时执行该语句块 --]
else
--[ 布尔表达式为 false 时执行该语句块 --]
end
案例:
--[ 定义变量 --]
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
Lua if...elseif...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
Lua 函数
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语言函数可以返回多个值,每个值以逗号隔开。
上面的语法等价于:
optional_function_scope function_name =function( argument1, argument2, argument3..., argumentn)
function_body
return result_params_comma_separated
end
案例:
--[[ 函数返回两个值的最大值 --]]
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函数可以返回多个结果值,比如string.find,其返回匹配串"开始和结束的下标"(如果不存在匹配串返回nil)。
案例:
ngx.say("string.find,其返回匹配串开始和结束的下标(如果不存在匹配串返回nil)");
s, e = string.find("www.runoob.com", "runoob")
ngx.say("s:"..s, " e:"..e)
输出结果:string.find,其返回匹配串开始和结束的下标(如果不存在匹配串返回nil)
s:5 e:10
function foo(a,b,c,...)
local sum = a+b
return sum,c --函数可以返回多个值
end
r1,r2 = foo(1,"123","hello")--平行赋值
print(r1,r2);
--输出结果
124 hello
注:使用函数的好处:1.降低程序的复杂性 2.增加程序的可读性 3.避免重复代码 4.隐含局部变量
如果参数列表为空,必须使用 ()
表明是函数调用。如function foo() end; 则调用foo();
Lua 运算符
Lua提供了以下几种运算符类型:算术运算符,关系运算符,逻辑运算符,其他运算符
算术运算符
下表列出了 Lua 语言中的常用算术运算符,设定 A 的值为10,B 的值为 20:
操作符 | 描述 | 实例 |
---|---|---|
+ | 加法 | A + B 输出结果 30 |
- | 减法 | A - B 输出结果 -10 |
* | 乘法 | A * B 输出结果 200 |
/ | 除法 | B / A w输出结果 2 |
% | 取余 | B % A 输出结果 0 |
^ | 乘幂 | A^2 输出结果 100 |
- | 负号 | -A 输出结果 -10 |
关系运算符
下表列出了 Lua 语言中的常用关系运算符,设定 A 的值为10,B 的值为 20:
操作符 | 描述 | 实例 |
---|---|---|
== | 等于,检测两个值是否相等,相等返回 true,否则返回 false | (A == B) 为 false。 |
~= | 不等于,检测两个值是否相等,相等返回 false,否则返回 true | (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 语言中字符串可以使用以下三种方式来表示:单引号间的一串字符、双引号间的一串字符、[[和]]间的一串字符
转义字符用于表示不能直接显示的字符,比如后退键,回车键,等。如在字符串转换双引号可以使用 "\""。
所有的转义字符和所对应的意义:
转义字符 | 意义 | 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 替换次数(可以忽略,则全部替换),如: |
4 | string.find (str, substr, [init, [end]]) 在一个指定的目标字符串中搜索指定的内容(第三个参数为索引),返回其开始位置和结束位置。不存在则返回 nil。 |
5 | string.reverse(arg) 字符串反转 |
6 | string.format(...) 返回一个类似printf的格式化字符串 |
7 | string.char(arg) 和 string.byte(arg[,int]) char 将整型数字转成字符并连接, byte 转换字符为整数值(可以指定某个字符,默认第一个字符)。 |
8 | string.len(arg) 计算字符串长度。 |
9 | string.rep(string, n) 返回字符串string的n个拷贝 |
10 | .. 链接两个字符串 |
11 | string.gmatch(str, pattern) 回一个迭代器函数,每一次调用这个函数,返回一个在字符串 str 找到的下一个符合 pattern 描述的子串。如果参数 pattern 描述的字符串没有找到,迭代函数返回nil。 |
12 | string.match(str, pattern, init) string.match()只寻找源字串str中的第一个配对. 参数init可选, 指定搜寻过程的起点, 默认为1。 在成功配对时, 函数将返回配对表达式中的所有捕获结果; 如果没有设置捕获标记, 则返回整个配对字符串. 当没有成功的配对时, 返回nil。 |
string.sub(s, i [, j]) | 返回字符串 s 中,索引 i 到索引 j 之间的子字符,若j没有则表示截取到末尾 |
案例:
function test19()
local str="hello lua";
ngx.say("string.sub(s, i [, j]) 返回字符串 s 中,索引 i 到索引 j 之间的子字符");
ngx.say(string.sub(str, 4, 7))
ngx.say(string.sub(str, 2))
end
--输出结果:
string.sub(s, i [, j]) 返回字符串 s 中,索引 i 到索引 j 之间的子字符
lo l
ello lua
--string.gsub(sourceStr,findStr,replaceStr,num) 替换指定字符串
print(string.gsub("hello lua","l","L",2))
--输出结构:
heLLo lua 2
字符串格式化
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位.
案例:
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
案例:string库常用函数
function test15()
ngx.say("字符串操作:");
--返回字符串s的长度
local s = "HelloWorld"
ngx.say("string.len 返回字符串s的长度 ",string.len(s)) -->10
--重复n次字符串s的串
ngx.say("string.rep(str,num) 重复n次字符串s的串 ",string.rep(s,2)) -->HelloWorldHelloWorld
--大写字母转换成小写
ngx.say("string.lower(s) 大写字母转换成小写 ",string.lower(s)) -->helloworld
--小写转换成大写
ngx.say("string.upper(s) 小写转换成大写",string.upper(s)) -->HELLOWORLD
--截取字符串
local s = "[in brackets]"
ngx.say("string.sub(s,start,end) 截取字符串 ",string.sub(s,2,-1)) -->in brackets]
--将每一个数字转换成字符
ngx.say("string.char(97) 将每一个数字转换成字符 ",string.char(97)) -->a
--将每一个字符转换成数字
ngx.say("string.byte('str') 将每一个字符转换成数字 ",string.byte("abc")) --> 97
ngx.say("string.byte('str') 将每一个字符转换成数字 ",string.byte("abc", 2)) --> 98
ngx.say("string.byte('str') 将每一个字符转换成数字 ",string.byte("abc", -1)) --> 99
--注:使用负数索引访问字符串的最后一个字符
--对字符串进行格式化输出
PI = 3.14165120
ngx.say("string.format(...) 对字符串进行格式化输出 ",string.format("pi = %.4f", PI)) -->pi = 3.1417
end
注:
string库中所有的字符索引从前往后是1,2,...;从后往前是-1,-2,...,下标从1开始
string库中所有的function都不会直接操作字符串,而是返回一个结果。
string.sub(str,start,end):字符串截取
在string库中功能最强大的函数是:string.find(字符串查找),string.gsub(全局字符串替换), string.gfind(全局字符串查找)。这些函数都是基于模式匹配的。
string.find函数第三个参数是可选的:标示目标串中搜索的起始位置。当我们想查找目标串中所有匹配的子串的时候,这个选项非常有用。
不写默认从1开始
function test16()
local s = "\nare you ok!\n OK\n"
local t = {}
local i = 0
while true do
i = string.find(s,"\n",i+1)
if i == nil then
break
end
table.insert(t,i)
end
for k,v in pairs(t) do
ngx.say(k,"->",v)
end
end
--输出结果:
1->1
2->13
3->17
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=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
以上代码执行输出结果为:
1
2
3
2
4
6
3
6
9
Lua 迭代器
泛型 for 迭代器
泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。
泛型 for 迭代器提供了集合的 key/value 对,语法格式如下:
for k, v in pairs(t) do
print(k, v)
end
案例:
function test13()
ngx.say("数值索引访问的表:{'banana','orange','apple'}");
ngx.say("泛型for pairs格式");
local fruits = {"banana","orange","apple" }
for k, v in pairs(fruits) do
ngx.say("key:",k," value:",v);
end
ngx.say("------------------");
ngx.say("泛型for ipairs格式");
for k, v in ipairs(fruits) do
ngx.say("key:",k," value:",v);
end
end
--输出结果:
数值索引访问的表:{'banana','orange','apple'}
泛型for pairs格式
key:1 value:banana
key:2 value:orange
key:3 value:apple
------------------
泛型for ipairs格式
key:1 value:banana
key:2 value:orange
key:3 value:apple
function test14()
ngx.say("键名索引访问的表:{id='11',name='tom',sex='F'}");
ngx.say("泛型for pairs格式");
local user={id="11",name="tom",sex="F"};
for k, v in pairs(user) do
ngx.say("key:",k," value:",v);
end
ngx.say("------------------");
ngx.say("泛型for ipairs格式");
for k, v in ipairs(user) do
ngx.say("key:",k," value:",v);
end
end
--输出结果:
键名索引访问的表:{id='11',name='tom',sex='F'}
泛型for pairs格式
key:sex value:F
key:name value:tom
key:id value:11
------------------
泛型for ipairs格式
pairs和ipairs区别:
pairs: 迭代table,可以遍历表中所有的key可以返回nil
ipairs: 迭代数组,不能返回nil,如果遇到nil则退出
pairs:函数可以支持纯数值型索引和混合索引table且无序输出。
ipairs:函数只能是数值型索引table,输出结果为连续数值型索引,若有中断则只输出连续的元素。
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 操作
以下列出了 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进行升序排序。 |
访问表的成员:通过“.”或者“[]”运算符来访问表的成员。
注意:表达式a.b等价于a[“b”],但不等价于a[b]
案例:
local tableDemo = {"hello" , name = "Erick" , ['city'] = "BeiJing"}
print(tableDemo.name)
print(tableDemo.city)
print(tableDemo['city'])
print(tableDemo[1])
--输出结果:
Erick
BeiJing
BeiJing
hello
在Lua中用table实现集合是非常简单的,见如下代码:
function test18()
reserved = {
["while"] = true, ["end"] = true,
["function"] = true, ["local"] = true,
}
for k,v in pairs(reserved) do
ngx.say(k,"->",v)
end
end
--输出结果
while->true
local->true
end->true
function->true
注:table的长度可以使用#获取,但是遇到不连续的索引则中断,所以使用时要注意连续,
案例:
local mytable = {[1] = 10, [2] = 100, [3] = 60, [5] =50}
print("table 长度" , #mytable)
-----输出结果------
table 长度 3
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 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。
lua_package_path 'src/?.lua;;src/app/?.lua';
文件路径以 ";" 号分隔,最后的 2 个 ";;" 表示新加的路径后面加上原来的默认路径。
Lua 文件 I/O
Lua I/O 库用于读取和处理文件。分为简单模式(和C一样)、完全模式。
- 简单模式(simple model)拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。
- 完全模式(complete model) 使用外部的文件句柄来实现。它以一种面对对象的形式,将所有的文件操作定义为文件句柄的方法
简单模式在做一些简单的文件操作时较为合适。但是在进行一些高级的文件操作的时候,简单模式就显得力不从心。例如同时读取多个文件这样的操作,使用完全模式则较为合适。
打开文件操作语句如下:
file = io.open (filename [, mode])
mode 的值有:
模式 | 描述 |
---|---|
r | 以只读方式打开文件,该文件必须存在。 |
w | 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。 |
a | 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留。(EOF符保留) |
r+ | 以可读写方式打开文件,该文件必须存在。 |
w+ | 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。 |
a+ | 与a类似,但此文件可读可写 |
b | 二进制模式,如果文件是二进制文件,可以加上b |
+ | 号表示对文件既可以读也可以写 |
简单模式
简单模式使用标准的 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.lua 文件的第一行信息,并在该文件最后一行添加了 lua 的注释。如我这边输出的是:
-- test.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("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 文件
read 的参数与简单模式一致。
其他方法:
-
file:seek(optional whence, optional offset): 设置和获取当前文件位置,成功则返回最终的文件位置(按字节),失败则返回nil加错误信息。参数 whence 值可以是:
不带参数file:seek()则返回当前位置,file:seek("set")则定位到文件头,file:seek("end")则定位到文件尾并返回文件大小- "set": 从文件头开始
- "cur": 从当前位置开始[默认]
- "end": 从文件尾开始
- offset:默认为0
-
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()
</pre>
<p>我这边输出的结果是:</p>
<pre>
st.lua 文件末尾--test
Lua 错误处理
pcall 和 xpcall、debug
Lua中处理错误,可以使用函数pcall(protected call)来包装需要执行的代码。
pcall接收一个函数和要传递给后者的参数,并执行,执行结果:有错误、无错误;返回值true或者或false, errorinfo。
语法格式如下
if pcall(function_name, ….) then
-- 没有错误
else
-- 一些错误
end
Lua 面向对象
LUA中最基本的结构是table,所以需要用table来描述对象的属性。
--account 类
Account = {balance = 0}
function Account.withdraw (v)
Account.balance = Account.balance - v
end
这个定义创建了一个新的函数,并且保存在Account对象的withdraw域内,下面我们可以这样调用:
Account.withdraw(100.00)
案例:
-- 元类
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.length)
访问成员函数
我们可以使用冒号 : 来访问类的成员函数:
r:printArea()
内存在对象初始化时分配。
完整案例:
-- 元类
Shape = {area = 0}
-- 基础类方法 new
function Shape:new (o,side)
o = o or {}
setmetatable(o, self)
self.__index = self
side = side or 0
self.area = side*side;
return o
end
-- 基础类方法 printArea
function Shape:printArea ()
print("面积为 ",self.area)
end
-- 创建对象
myshape = Shape:new(nil,10)
myshape:printArea()
执行以上程序,输出结果为:
面积为 100
成员方法的定义:
function obj:method(a1, a2, ...)
...
end
等价于
function obj.method(self, a1, a2, ...)
...
end
等价于
obj.method = function (self, a1, a2, ...)
...
end
成员方法的调用:obj:method(a1, a2, ...) 等价于 obj.method(obj, a1, a2, ...)
案例:
function create(name,id)
local obj = {name = name,id = id}
function obj:SetName(name)
self.name = name
end
function obj:GetName()
return self.name
end
function obj:SetId(id)
self.id = id
end
function obj:GetId()
return self.id
end
return obj
end
local myCreate = create("sam",001)
print("myCreate's name:",myCreate:GetName(),"myCreate's id:",myCreate.GetId(myCreate))
myCreate:SetId(100)
myCreate:SetName("Hello Kity")
print("myCreate's name:",myCreate:GetName(),"myCreate's id:",myCreate:GetId())
输出结果:
myCreate's name:sam myCreate's id:1
myCreate's name:Hello Kity myCreate's id:100
lua编程中,经常遇到函数的定义和调用,有时候用点号调用,有时候用冒号调用,这里简单的说明一下原理。如:
点号调用:
-- 点号定义和点号调用:
girl = {money = 200}
function girl.goToMarket(girl ,someMoney)
girl.money = girl.money - someMoney
end
girl.goToMarket(girl ,100)
print(girl.money)
--输出结果
100
引用参数self:
-- 参数self指向调用者自身(类似于c++里的this 指向当前类)
girl = {money = 200}
function girl.goToMarket(self ,someMoney)
self.money = self.money - someMoney
end
girl.goToMarket(girl, 100)
print(girl.money)
--输出结果
100
冒号调用:
-- 冒号定义和冒号调用:
girl = {money = 200}
function girl:goToMarket(someMoney)
self.money = self.money - someMoney
end
girl:goToMarket(100)
print(girl.money)
--输出结果
100
冒号定义和冒号调用其实跟上面的效果一样,只是把第一个隐藏参数省略了,而该参数self指向调用者自身。
总结:
冒号只是起了省略第一个参数self的作用,该self指向调用者本身,并没有其他特殊的地方。
冒号操作会带入一个self参数,用来代表自己,而点号操作只是内容的展开。
在函数定义时,使用冒号将默认接收一个self参数,而使用点号则需要显式传入self参数。