OpenRestry实战五:lua基本语法

准备工作:

创建案例参考: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 替换次数(可以忽略,则全部替换),如:
> string.gsub("aaaa","a","z",3);
zzza    3
4 string.find (str, substr, [init, [end]])
在一个指定的目标字符串中搜索指定的内容(第三个参数为索引),返回其开始位置和结束位置。不存在则返回 nil。
> 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"
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()

我这边输出的结果是:

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参数。

 

 

你可能感兴趣的:(服务器)