Lua

Lua

特性:轻量级高效支持面向过程编程和函数式编程自动内存管理

Lua的特性:

  • 变量名没有类型,值才有类型,变量名在运行时可与任何类型的值绑定
  • 只有唯一的数据结构表(table),混合了数组,哈希,可以用任何类型的值作为key和value

Lua代码编写规范

命名规范

  1. 所有lua文件名,使用小写字母、下划线
  2. 类名,变量名尽可能使用有意义的英文,变量使用驼峰命名

Lua应用场景

  • 游戏开发
  • 独立应用脚本
  • web应用脚本
  • 安全系统,如入侵检测系统

第一个lua程序

print("hello world")

Lua 环境安装

Linux系统上安装

只需下载源码包并在终端解压编译即可。

安装:

curl -R -O http://www.lua.org/ftp/lua-5.3.0.tar.gz
tar zxf lua-5.3.0.tar.gz
cd lua-5.3.0
make linux test
make install

Window 系统上安装 Lua

window 下你可以使用一个叫 "SciTE" 的 IDE环 境来执行 lua 程序,下载地址为:

双击安装后即可在该环境下编写 Lua 程序并运行。

你也可以使用 Lua 官方推荐的方法使用 LuaDist:http://luadist.org/

Lua 基本语法

第一个Lua程序

交互式编程

Lua提供了交互式编程模式。可以在命令行中输入程序并立即查看效果

Lua交互式模式可以通过命令lua -ilua 来启用

image-20240514093926022

脚本式编程

可以将lua脚本保存到一个以lua结尾的文件,并执行,该模式称为脚本式编程。

image-20240514094212840

我们也可以将代码修改为如下形式来执行脚本(在开头添加:#!/usr/local/bin/lua)

#!/usr/local/bin/lua

print("Hello World!")
print("www.runoob.com")

以上代码中,指定了Lua的解释器/usr/local/bin directory。加上 # 号标记解释器会忽略它。接下来我们为脚本添加可执行权限,并执行:

image-20240514094433760

注释

单行注释

-- 两个减号是单行注释

多行注释

--[[

​ 多行注释

​ 多行注释

--]]

标识符

lua不允许使用特殊字符如 @ $ % 来定义标识符,区分大小写。

关键字

and break do else
elseif end false for
function if in local
nil not or repeat
return then true until
while goto

一般约定,以下划线开头链接一串大写字母的名字(比如 _VERSION)被保留用于Lua内部全局变量

全局变量

在默认情况下,变量总是认为是全局的

全局变量不需要声明,给一个变量赋值后立即创建了这个全局变量,访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil

Lua 5.1.5  Copyright (C) 1994-2012 Lua.org, PUC-Rio
> print(b)
nil
> b=10
> print(b)
10

如果想要删除一个全局变量,则只需将变量赋值为nil

b = nil
print(b)   -- >nil

Lua 数据类型

Lua 是动态类型语言,变量不要类型定义,只需要为变量赋值。 值可以存储在变量中,作为参数传递或结果返回。

Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。

数据类型 描述
nil 这个最简单,只有值nil属于该类,表示一个无效值(在条件表达式中相当于false)。
boolean 包含两个值:false和true。
number 表示双精度类型的实浮点数
string 字符串由一对双引号或单引号来表示
function 由 C 或 Lua 编写的函数
userdata 表示任意存储在变量中的C数据结构
thread 表示执行的独立线路,用于执行协同程序
table Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字、字符串或表类型。在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。

我们可以使用 type 函数测试给定变量或者值的类型:

print(type("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
 
tab1.key1 = nil
for k, v in pairs(tab1) do
    print(k .. " - " .. v)
end

nil 作比较时应该加上双引号 "

> type(X)
nil
> type(X)==nil
false
> type(X)=="nil"
true

type(X)==nil 结果为 false 的原因是 type(X) 实质是返回的 "nil" 字符串,是一个 string 类型:

type(type(X))==string

openResty的Lua接口还提供了一种特殊的空值,即ngx.null,用来表示不同于nil的空值


boolean(布尔类型)

boolean 类型只有两个可选值:true(真) 和 false(假),Lua 把 false 和 nil 看作是 false其他的都为 true,数字 0 也是 true:

print(type(true))
print(type(false))
print(type(nil))
 
if false or nil then
    print("至少有一个是 true")
else
    print("false 和 nil 都为 false")
end

if 0 then
    print("数字 0 是 true")
else
    print("数字 0 为 false")
end

以上代码执行结果如下:

$ lua test.lua 
boolean
boolean
nil
false 和 nil 都为 false
数字 0 是 true

number(数字)

Lua 默认只有一种 number 类型 -- double(双精度)类型(默认类型可以修改 luaconf.h 里的定义),

可以使用数学函数math.floor(向下取整)math.ceil(向上取整)进行取整操作

以下几种写法都被看作是 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>
  菜鸟教程
</body>
</html>
]]*
print(html)

以下代码执行结果为:

<html>
<head></head>
<body>
    
</body>
</html>

在对一个数字字符串上进行算术操作时,Lua 会尝试将这个数字字符串转成一个数字:

> print("2" + 6)
8.0
> print("2" + "6")
8.0
> print("2 + 6")
2 + 6
> print("-2e2" * "6")
-1200.0
> print("error" + 1)
stdin:1: attempt to perform arithmetic on a string value
stack traceback:
        stdin:1: in main chunk
        [C]: in ?
>

以上代码中"error" + 1执行报错了,字符串连接使用的是 .. ,如:

> print("a" .. 'b')
ab
> print(157 .. 428)
157428
> 

使用 # 来计算字符串的长度,放在字符串前面,如下实例:

> len = "www.runoob.com"
> print(#len)
14
> print(#"www.runoob.com")
14
>

table(表)

在 Lua 里,table 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。也可以在表里添加一些数据,直接初始化表:

-- 创建一个空的 table
local tbl1 = {}
 
-- 直接初始表
local tbl2 = {"apple", "pear", "orange", "grape"}

Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。

-- table_test.lua 脚本文件
a = {}
a["key"] = "value"
key = 10
a[key] = 22
a[key] = a[key] + 11
for k, v in pairs(a) do
    print(k .. " : " .. v)
end

脚本执行结果为:

$ lua table_test.lua 
key : value
10 : 33

不同于其他语言的数组把 0 作为数组的初始索引,在 Lua 里表的默认初始索引一般以 1 开始。

-- table_test2.lua 脚本文件
local tbl = {"apple", "pear", "orange", "grape"}
for key, val in pairs(tbl) do
    print("Key", key)
end

脚本执行结果为:

$ lua table_test2.lua 
Key    1
Key    2
Key    3
Key    4

table 不会固定长度大小,有新数据添加时 table 长度会自动增长,没初始的 table 都是 nil。

-- table_test3.lua 脚本文件
a3 = {}
for i = 1, 10 do
    a3[i] = i
end
a3["key"] = "val"
print(a3["key"])
print(a3["none"])

脚本执行结果为:

$ lua table_test3.lua 
val
nil

function(函数)

在 Lua 中,函数是被看作是"第一类值(First-Class Value)",函数可以存在变量里:

-- function_test.lua 脚本文件
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))

脚本执行结果为:

$ lua function_test.lua 
120
120

function 可以以匿名函数(anonymous function)的方式通过参数传递:

-- function_test2.lua 脚本文件
function testFun(tab,fun)
        for k ,v in pairs(tab) do
                print(fun(k,v));
        end
end

tab={key1="val1",key2="val2"};
testFun(tab,
function(key,val)--匿名函数
        return key.."="..val;
end

脚本执行结果为:

$ lua function_test2.lua 
key1=val1
key2=val2

thread(线程)

在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。

线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。


userdata(自定义类型)

userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。

Lua变量

变量在使用前,需要在代码中进行声明,即创建该变量。

编译程序执行代码之前编译器需要知道如何给语句变量开辟存储区,用于存储变量的值。

Lua 变量有三种类型:全局变量局部变量表中的域

Lua 中的变量全是全局变量,哪怕是语句块或是函数里,除非用 local 显式声明为局部变量

局部变量的作用域为从声明位置开始到所在语句块结束。

变量的默认值均为 nil。

-- test.lua 文件脚本
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

赋值语句

赋值是改变一个变量的值和改变表域的最基本的方法。

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, c = 0, 0, 0
print(a,b,c)             --> 0   0   0

多值赋值经常用来交换变量,或将函数调用返回给变量:

a, b = f()

f()返回两个值,第一个赋给a,第二个赋给b。

应该尽可能的使用局部变量,有两个好处:

    1. 避免命名冲突。
    1. 访问局部变量的速度比全局变量更快。

索引

对 table 的索引使用方括号 []。Lua 也提供了 . 操作。

t[i]
t.i                 -- 当索引为字符串类型时的一种简化写法
gettable_event(t,i) -- 采用索引访问本质上是一个类似这样的函数调用
> site = {}
> site["key"] = "www.runoob.com"
> print(site["key"])
www.runoob.com
> print(site.key)
www.runoob.com

Lua循环

很多情况下我们需要做一些有规律性的重复操作,因此在程序中就需要重复执行某些语句。

一组被重复执行的语句称之为循环体,能否继续重复,决定循环的终止条件。

循环结构是在一定条件下反复执行某段程序的流程结构,被反复执行的程序被称为循环体。

循环语句是由循环体及循环的终止条件两部分组成的。

Lua 语言提供了以下几种循环处理方式:

循环类型 描述
while 循环 在条件为 true 时,让程序重复地执行某些语句。执行语句前会先检查条件是否为 true。
for 循环 重复执行指定语句,重复次数可在 for 语句中控制。
repeat...until 重复执行循环,直到 指定的条件为真时为止
循环嵌套 可以在循环内嵌套一个或多个循环语句(while do ... end;for ... do ... end;repeat ... until;)

循环控制语句

循环控制语句用于控制程序的流程, 以实现程序的各种结构方式。

Lua 支持以下循环控制语句:

控制语句 描述
break 语句 退出当前循环或语句,并开始脚本执行紧接着的语句。
goto 语句 将程序的控制点转移到一个标签处。

无限循环

在循环体中如果条件永远为 true 循环语句就会永远执行下去,以下以 while 循环为例:

while(true)
do
    print("循环会被永远执行下去")
end

流程控制

Lua 编程语言流程控制语句通过程序设定一个或多个条件语句来设定。在条件为 true 时执行指定程序代码,在条件为 false 时执行其他指定代码。

以下是典型的流程控制流程图:

控制结构的条件表达式结果可以是任何值,Lua认为false和nil为假,true和非nil为真。

要注意的是Lua中 0 为 true:

--[ 0 为 true ]
if (0) then
    print("0 为 true")
end

Lua 提供了以下控制结构语句:

语句 描述
if 语句 if 语句 由一个布尔表达式作为条件判断,其后紧跟其他语句组成。
if...else 语句 if 语句 可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码。
if 嵌套语句 你可以在ifelse if中使用一个或多个 ifelse if 语句 。

if语句

Lua if 语句 由一个布尔表达式作为条件判断,其后紧跟其他语句组成。

语法格式如下:

if(布尔表达式) then
   --[ 在布尔表达式为 true 时执行的语句 --]
end

Lua认为false和nil为假,true 和非nil为真。要注意的是Lua中 0 为 true。

--[ 定义变量 --]
a = 10;

--[ 使用 if 语句 --]
if( a < 20 )
then
   --[ if 条件为 true 时打印以下信息 --]
   print("a 小于 20" );
end
print("a 的值为:", a);

if - else 语句

Lua if 语句可以与 else 语句搭配使用, 在 if 条件表达式为 false 时执行 else 语句代码块。

具体格式:

if(布尔表达式)
then
   --[ 布尔表达式为 true 时执行该语句块 --]
else
   --[ 布尔表达式为 false 时执行该语句块 --]
end

if...elseif...else 语句

Lua if 语句可以与 elseif...else 语句搭配使用, 在 if 条件表达式为 false 时执行 elseif...else 语句代码块,用于检测多个条件语句。

Lua if...elseif...else 语句语法格式如下:

if( 布尔表达式 1)
then
   --[ 在布尔表达式 1 为 true 时执行该语句块 --]

elseif( 布尔表达式 2)
then
   --[ 在布尔表达式 2 为 true 时执行该语句块 --]

elseif( 布尔表达式 3)
then
   --[ 在布尔表达式 3 为 true 时执行该语句块 --]
else 
   --[ 如果以上布尔表达式都不为 true 则执行该语句块 --]
end
--[ 定义变量 --]
a = 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 )

函数

在Lua中,函数是对语句和表达式进行抽象的主要方法。既可以用来处理一些特殊的工作,也可以用来计算一些值。

Lua 提供了许多的内建函数,你可以很方便的在程序中调用它们,如print()函数可以将传入的参数打印在控制台上。

Lua 函数主要有两种用途:

  • 1.完成指定的任务,这种情况下函数作为调用语句使用;
  • 2.计算并返回值,这种情况下函数作为赋值语句的表达式使用。

函数定义

Lua 编程语言函数定义格式如下:

optional_function_scope function function_name( argument1, argument2, argument3..., argumentn)
    function_body
    return result_params_comma_separated
end

解析:

  • optional_function_scope: 该参数是可选的指定函数是全局函数还是局部函数,未设置该参数默认为全局函数,如果你需要设置函数为局部函数需要使用关键字 local。
  • function_name: 指定函数名称。
  • argument1, argument2, argument3..., argumentn: 函数参数,多个参数以逗号隔开,函数也可以不带参数。
  • function_body: 函数体,函数中需要执行的代码语句块。
  • result_params_comma_separated: 函数返回值,Lua语言函数可以返回多个值,每个值以逗号隔开。
--[[ 函数返回两个值的最大值 --]]
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))

多值返回

Lua函数可以返回多个结果值,比如string.find,其返回匹配串“开始和结束的下表” (如果不存在匹配串返回nil)

> s, e = string.find("www.runoob.com", "runoob") 
> print(s, e)
5    10

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}))

可变参数

lua函数可以接受可变数目的参数,在函数列表中使用三点 ... 表示函数有可变的参数

function add(...)  
local s = 0  
  for i, v in ipairs{...} do   --> {...} 表示一个由所有变长参数构成的数组  
    s = s + v  
  end  
  return s  
end  
print(add(3,4,5,6,7))  --->25
function average(...)
   result = 0
   local arg={...}    --> arg 为一个表,局部变量
   for i,v in ipairs(arg) do
      result = result + v
   end
   print("总共传入 " .. #arg .. " 个数")
   return result/#arg
end

print("平均值为",average(10,5,3,4,5,6))

我们也可以通过 select("#",...) 来获取可变参数的数量:

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 和 2

通常在遍历变长参数的时候只需要使用 {…},然而变长参数可能会包含一些 nil,那么就可以用 select 函数来访问变长参数了:select('#', …) 或者 select(n, …)

  • select('#', …) 返回可变参数的长度。

  • select(n, …) 用于返回从起点 n 开始到结束位置的所有参数列表。

调用 select 时,必须传入一个固定实参 selector(选择开关) 和一系列变长参数。如果 selector 为数字 n,那么 select 返回参数列表中从索引 n 开始到结束位置的所有参数列表,否则只能为字符串 #,这样 select 返回变长参数的总数。

function f(...)
    a = select(3,...)  -->从第三个位置开始,变量 a 对应右边变量列表的第一个参数
    print (a)
    print (select(3,...)) -->打印所有列表参数
end

f(0,1,2,3,4,5)
do  
    function foo(...)  
        for i = 1, select('#', ...) do  -->获取参数总数
            local arg = select(i, ...); -->读取参数,arg 对应的是右边变量列表的第一个参数
            print("arg", arg);  
        end  
    end  
  
    foo(1, 2, 3, 4);  
end

运算符

逻辑运算符

下表列出了 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。
a = true
b = true

if ( a and b )
then
   print("a and b - 条件为 true" )
end

if ( a or b )
then
   print("a or b - 条件为 true" )
end

print("---------分割线---------" )

-- 修改 a 和 b 的值
a = false
b = true

if ( a and b )
then
   print("a and b - 条件为 true" )
else
   print("a and b - 条件为 false" )
end

if ( not( a and b) )
then
   print("not( a and b) - 条件为 true" )
else
   print("not( a and b) - 条件为 false" )
end

其他运算符

下表列出了 Lua 语言中的连接运算符与计算表或字符串长度的运算符:

操作符 描述 实例
.. 连接两个字符串 a..b ,其中 a 为 "Hello " , b 为 "World", 输出结果为 "Hello World"。
# 一元运算符,返回字符串或表的长度。 #"Hello" 返回 5
a = "Hello "
b = "World"

print("连接字符串 a 和 b ", a..b )

print("b 字符串长度 ",#b )

print("字符串 Test 长度 ",#"Test" )

print("菜鸟教程网址长度 ",#"www.runoob.com" )

Lua 数组

数组就是相同数据类型的元素按照一定顺序排列的集合,可以是一维数组和多维数组

在lua中,数组不是一种特定的数据类型,而是一种用来存储一组值的数据结构

实际上,Lua 中并没有专门的数组类型,而是使用一种被称为 "table" 的数据结构来实现数组的功能。

Lua 数组的索引键值可以使用整数表示,数组的大小不是固定的。

在 Lua 索引值是以 1 为起始,但你也可以指定 0 开始。

一维数组

一维数组是简单的数组,其逻辑结构是线性表

使用索引访问数组元素

-- 创建一个数组
local myArray = {10, 20, 30, 40, 50}

-- 访问数组元素
print(myArray[1])  -- 输出 10
print(myArray[3])  -- 输出 30

要计算数组的长度(即数组中元素的个数),你可以使用 # 操作符:

local myArray = {10, 20, 30, 40, 50}

-- 计算数组长度
local length = #myArray

print(length) -- 输出 5

一维数组可以使用for循环出数组中的元素,如下实例:

-- 创建一个数组
local myArray = {10, 20, 30, 40, 50}

-- 循环遍历数组
for i = 1, #myArray do
    print(myArray[i])
end

模块与库

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的函数用来加载模块。

在lua中创建一个模块最简单方法是:创建一个table,并将所有所需要导出的函数放入其中,最后返回这个table就可以了,例如

把下面的代码保存在my.lua中

local _M = {}

local function get_name()
    return "Lucy"
end

function _M.greeting()
    print("hello " .. get_name())
end

return _M

要加载一个模块,只需要简单地调用就可以了。例如

require("<模块名>")
local my_module = require('my')
my_module.greeting()

或者

require "<模块名>"

执行 require 后会返回一个由模块常量或函数组成的 table,并且还会定义一个包含该 table 的全局变量。

-- test_module.lua 文件
-- module 模块为上文提到到 module.lua
require("module")
 
print(module.constant)
 
module.func3()

或者给加载的模块定义一个别名变量,方便调用:

-- test_module2.lua 文件
-- module 模块为上文提到到 module.lua
-- 别名变量 m
local m = require("module")
 
print(m.constant)
 
m.func3()

string库

字符串库中的所有函数都导出在模块string中

string.byte(s [, i [, j ]])

返回字符 s[i]、s[i + 1]、s[i + 2]、······、s[j] 所对应的 ASCII 码。i 的默认值为 1,即第一个字节,j 的默认值为 i 。

print(string.byte("abc", 1, 3))
print(string.byte("abc", 3)) -- 缺少第三个参数,第三个参数默认与第二个相同,此时为 3
print(string.byte("abc"))    -- 缺少第二个和第三个参数,此时这两个参数都默认为 1

-->output
97    98    99
99
97

string.char (...)

接收 0 个或更多的整数(整数范围:0~255),返回这些整数所对应的 ASCII 码字符组成的字符串。当参数为空时,默认是一个 0。

print(string.char(96, 97, 98))
print(string.char())        -- 参数为空,默认是一个0,
                            -- 你可以用string.byte(string.char())测试一下
print(string.char(65, 66))

--> output
`ab

AB

string.upper(s)

接收一个字符串转为全大写

string.lower(s)

接收一个字符串转为全小写

string.len(s)

接收一个字符串返回它的长度

string.find(s, p [, init [, plain]])

在 s 字符串中第一次匹配 p 字符串。若匹配成功,则返回 p 字符串在 s 字符串中出现的开始位置和结束位置;若匹配失败,则返回 nil。 第三个参数 init 默认为 1,并且可以为负整数,当 init 为负数时,表示从 s 字符串的 string.len(s) + init + 1 索引处开始向后匹配字符串 p 。 第四个参数默认为 false,当其为 true 时,只会把 p 看成一个字符串对待。

local find = string.find
print(find("abc cba", "ab"))
print(find("abc cba", "ab", 2))     -- 从索引为2的位置开始匹配字符串:ab
print(find("abc cba", "ba", -1))    -- 从索引为7的位置开始匹配字符串:ba
print(find("abc cba", "ba", -3))    -- 从索引为5的位置开始匹配字符串:ba
print(find("abc cba", "(%a+)", 1))  -- 从索引为1处匹配最长连续且只含字母的字符串
print(find("abc cba", "(%a+)", 1, true)) --从索引为1的位置开始匹配字符串:(%a+)

-->output
1   2
nil
nil
6   7
1   3   abc
nil

string.format(formatstring, ...)

一个指示由字符 % 加上一个字母组成,这些字母指定了如何格式化参数,例如 d 用于十进制数、x 用于十六进制数、o 用于八进制数、f 用于浮点数、s 用于字符串等。在字符 % 和字母之间可以再指定一些其他选项,用于控制格式的细节。

print(string.format("%.4f", 3.1415926))     -- 保留4位小数
print(string.format("%d %x %o", 31, 31, 31))-- 十进制数31转换成不同进制
d = 29; m = 7; y = 2015                     -- 一行包含几个语句,用;分开
print(string.format("%s %02d/%02d/%d", "today is:", d, m, y))

-->output
3.1416
31 1f 37
today is: 29/07/2015

string.match(s, p [, init])

在字符串 s 中匹配(模式)字符串 p,若匹配成功,则返回目标字符串中与模式匹配的子串;否则返回 nil。第三个参数 init 默认为 1,并且可以为负整数,当 init 为负数时,表示从 s 字符串的 string.len(s) + init + 1 索引处开始向后匹配字符串 p。

print(string.match("hello lua", "lua"))
print(string.match("lua lua", "lua", 2))  --匹配后面那个lua
print(string.match("lua lua", "hello"))
print(string.match("today is 27/7/2015", "%d+/%d+/%d+"))

-->output
lua
lua
nil
27/7/2015

string.match 目前并不能被 JIT 编译,应 尽量 使用 ngx_lua 模块提供的 ngx.re.match 等接口。

string.rep(s, n)

返回字符串 s 的 n 次拷贝。

string.reverse (s)

接收一个字符串 s,返回这个字符串的反转。

string.gsub(s, p, r [, n])

将目标字符串 s 中所有的子串 p 替换成字符串 r。可选参数 n,表示限制替换次数。返回值有两个,第一个是被替换后的字符串,第二个是替换了多少次。

table库

table库是一些辅助函数构成的,这些函数将table作为数组来操作

在lua中,数组下表从1开始计数

table.getn 获取长度

table.concat (table [, sep [, i [, j ] ] ])

local a = {1, 3, 5, "hello" }
print(table.concat(a))              -- output: 135hello
print(table.concat(a, "|"))         -- output: 1|3|5|hello
print(table.concat(a, " ", 4, 2))   -- output:
print(table.concat(a, " ", 2, 4))   -- output: 3 5 hello

table.insert (table, [pos ,] value)

local a = {1, 8}             --a[1] = 1,a[2] = 8
table.insert(a, 1, 3)   --在表索引为1处插入3
print(a[1], a[2], a[3])
table.insert(a, 10)    --在表的最后插入10
print(a[1], a[2], a[3], a[4])

-->output
3    1    8
3    1    8    10

table.maxn (table)

返回(数组型)表 table 的最大索引编号;如果此表没有正的索引编号,返回 0。

当长度省略时,此函数通常需要 O(n) 的时间复杂度来计算 table 的末尾。因此用这个函数省略索引位置的调用形式来作 table 元素的末尾追加,是高代价操作。

local a = {}
a[-1] = 10
print(table.maxn(a))
a[5] = 10
print(table.maxn(a))

-->output
0
5

table.remove (table [, pos])

local a = { 1, 2, 3, 4}
print(table.remove(a, 1)) --删除速索引为1的元素
print(a[1], a[2], a[3], a[4])

print(table.remove(a))   --删除最后一个元素
print(a[1], a[2], a[3], a[4])

-->output
1
2    3    4    nil
4
2    3    nil    nil

table.sort (table [, comp])

按照给定的比较函数 comp 给表 table 排序,也就是从 table[1] 到 table[n],这里 n 表示 table 的长度。 比较函数有两个参数,如果希望第一个参数排在第二个的前面,就应该返回 true,否则返回 false。 如果比较函数 comp 没有给出,默认从小到大排序。

local function compare(x, y) --从大到小排序
   return x > y         --如果第一个参数大于第二个就返回true,否则返回false
end

local a = { 1, 7, 3, 4, 25}
table.sort(a)           --默认从小到大排序
print(a[1], a[2], a[3], a[4], a[5])
table.sort(a, compare) --使用比较函数进行排序
print(a[1], a[2], a[3], a[4], a[5])

-->output
1    3    4    7    25
25    7    4    3    1

日期时间函数(不推荐使用)

在lua中,函数time,date,difftime提供了所有的日期和时间功能

在OpenResty中不推荐使用这里的标准时间函数,推荐使用ngx_lua模块提供的带缓存的时间接口,如ngx.today, ngx.time, ngx.utctime, ngx.localtime, ngx.now, ngx.http_time,以及 ngx.cookie_time 等。

os.time ([table])

字段名称 取值范围
year 四位数字
month 1--12
day 1--31
hour 0--23
min 0--59
sec 0--61
isdst boolean(true表示夏令时)
print(os.time())    -->output  1438243393
a = { year = 1970, month = 1, day = 1, hour = 8, min = 1 }
print(os.time(a))   -->output  60

os.difftime (t2, t1)

local day1 = { year = 2015, month = 7, day = 30 }
local t1 = os.time(day1)

local day2 = { year = 2015, month = 7, day = 31 }
local t2 = os.time(day2)
print(os.difftime(t2, t1))   -->output  86400

os.date ([format [, time]])

local tab1 = os.date("*t")  --返回一个描述当前日期和时间的表
local ans1 = "{"
for k, v in pairs(tab1) do  --把tab1转换成一个字符串
    ans1 = string.format("%s %s = %s,", ans1, k, tostring(v))
end

ans1 = ans1 .. "}"
print("tab1 = ", ans1)


local tab2 = os.date("*t", 360)  --返回一个描述日期和时间数为360秒的表
local ans2 = "{"
for k, v in pairs(tab2) do      --把tab2转换成一个字符串
    ans2 = string.format("%s %s = %s,", ans2, k, tostring(v))
end

ans2 = ans2 .. "}"
print("tab2 = ", ans2)

-->output
tab1 = { hour = 17, min = 28, wday = 5, day = 30, month = 7, year = 2015, sec = 10, yday = 211, isdst = false,}
tab2 = { hour = 8, min = 6, wday = 5, day = 1, month = 1, year = 1970, sec = 0, yday = 1, isdst = false,}
格式字符 含义
%a 一星期中天数的简写(例如:Wed)
%A 一星期中天数的全称(例如:Wednesday)
%b 月份的简写(例如:Sep)
%B 月份的全称(例如:September)
%c 日期和时间(例如:07/30/15 16:57:24)
%d 一个月中的第几天[01 ~ 31]
%H 24小时制中的小时数[00 ~ 23]
%I 12小时制中的小时数[01 ~ 12]
%j 一年中的第几天[001 ~ 366]
%M 分钟数[00 ~ 59]
%m 月份数[01 ~ 12]
%p “上午(am)”或“下午(pm)”
%S 秒数[00 ~ 59]
%w 一星期中的第几天[1 ~ 7 = 星期天 ~ 星期六]
%x 日期(例如:07/30/15)
%X 时间(例如:16:57:24)
%y 两位数的年份[00 ~ 99]
%Y 完整的年份(例如:2015)
%% 字符'%'

数学库

函数名 函数功能
math.rad(x) 角度x转换成弧度
math.deg(x) 弧度x转换成角度
math.max(x, ...) 返回参数中值最大的那个数,参数必须是number型
math.min(x, ...) 返回参数中值最小的那个数,参数必须是number型
math.random ([m [, n]]) 不传入参数时,返回 一个在区间[0,1)内均匀分布的伪随机实数;只使用一个整数参数m时,返回一个在区间[1, m]内均匀分布的伪随机整数;使用两个整数参数时,返回一个在区间[m, n]内均匀分布的伪随机整数
math.randomseed (x) 为伪随机数生成器设置一个种子x,相同的种子将会生成相同的数字序列
math.abs(x) 返回x的绝对值
math.fmod(x, y) 返回 x对y取余数
math.pow(x, y) 返回x的y次方
math.sqrt(x) 返回x的算术平方根
math.exp(x) 返回自然数e的x次方
math.log(x) 返回x的自然对数
math.log10(x) 返回以10为底,x的对数
math.floor(x) 返回最大且不大于x的整数
math.ceil(x) 返回最小且不小于x的整数
math.pi 圆周率
math.sin(x) 求弧度x的正弦值
math.cos(x) 求弧度x的余弦值
math.tan(x) 求弧度x的正切值
math.asin(x) 求x的反正弦值
math.acos(x) 求x的反余弦值
math.atan(x) 求x的反正切值

文件库

lua I/O库提供两种不同的方式处理文件:隐式文件描述,显示文件描述

这些I/O操作,在OpenResty的上下文中对事件循环是会产生阻塞效应。OpenResty比较擅长的是高并发网络处理,因此,在OpenResty总尽可能让网络处理部分,文件I/O操作部分相互独立

隐式文件描述

设置一个默认的输入或输出文件,然后在这个文件上进行所有的输入或输出操作。所有的操作函数由 io 表提供。

打开已经存在的 test1.txt 文件,并读取里面的内容

file = io.input("test1.txt")    -- 使用 io.input() 函数打开文件

repeat
    line = io.read()            -- 逐行读取内容,文件结束时返回nil
    if nil == line then
        break
    end
    print(line)
until (false)

io.close(file)                  -- 关闭文件

--> output
my test file
hello
lua

test1.txt 文件的最后添加一行 "hello world"

file = io.open("test1.txt", "a+")   -- 使用 io.open() 函数,以添加模式打开文件
io.output(file)                     -- 使用 io.output() 函数,设置默认输出文件
io.write("\nhello world")           -- 使用 io.write() 函数,把内容写到文件
io.close(file)

显示文件描述

使用 file:XXX() 函数方式进行操作, 其中 file 为 io.open() 返回的文件句柄。

打开已经存在的 test2.txt 文件,并读取里面的内容

file = io.open("test2.txt", "r")    -- 使用 io.open() 函数,以只读模式打开文件

for line in file:lines() do         -- 使用 file:lines() 函数逐行读取文件
   print(line)
end

file:close()

-->output
my test2
hello lua

在 test2.txt 文件的最后添加一行 "hello world"

file = io.open("test2.txt", "a")  -- 使用 io.open() 函数,以添加模式打开文件
file:write("\nhello world")       -- 使用 file:write() 函数,在文件末尾追加内容
file:close()

文件操作函数

io.open (filename [, mode])

按指定的模式 mode,打开一个文件名为 filename 的文件,成功则返回文件句柄,失败则返回 nil 加错误信息。模式:

模式 含义 文件不存在时
"r" 读模式 (默认) 返回nil加错误信息
"w" 写模式 创建文件
"a" 添加模式 创建文件
"r+" 更新模式,保存之前的数据 返回nil加错误信息
"w+" 更新模式,清除之前的数据 创建文件
"a+" 添加更新模式,保存之前的数据,在文件尾进行添加 创建文件

模式字符串后面可以有一个 'b',用于在某些系统中打开二进制文件。

注意 "w" 和 "wb" 的区别

  • "w" 表示文本文件。某些文件系统(如 Linux 的文件系统)认为 0x0A 为文本文件的换行符,Windows 的文件系统认为 0x0D0A 为文本文件的换行符。为了兼容其他文件系统(如从 Linux 拷贝来的文件),Windows 的文件系统在写文件时,会在文件中 0x0A 的前面加上 0x0D。使用 "w",其属性要看所在的平台。
  • "wb" 表示二进制文件。文件系统会按纯粹的二进制格式进行写操作,因此也就不存在格式转换的问题。(Linux 文件系统下 "w" 和 "wb" 没有区别)

file:close ()

关闭文件。注意:当文件句柄被垃圾收集后,文件将自动关闭。句柄将变为一个不可预知的值。

io.close ([file])

关闭文件,和 file:close() 的作用相同。没有参数 file 时,关闭默认输出文件。

file:flush ()

把写入缓冲区的所有数据写入到文件 file 中。

io.flush ()

相当于 file:flush(),把写入缓冲区的所有数据写入到默认输出文件。

io.input ([file])

当使用一个文件名调用时,打开这个文件(以文本模式),并设置文件句柄为默认输入文件; 当使用一个文件句柄调用时,设置此文件句柄为默认输入文件; 当不使用参数调用时,返回默认输入文件句柄。

file:lines ()

返回一个迭代函数, 每次调用将获得文件中的一行内容, 当到文件尾时,将返回 nil,但不关闭文件。

io.lines ([filename])

打开指定的文件 filename 为读模式并返回一个迭代函数, 每次调用将获得文件中的一行内容, 当到文件尾时,将返回 nil,并自动关闭文件。若不带参数时 io.lines() 等价于 io.input():lines() 读取默认输入设备的内容,结束时不关闭文件。

io.output ([file])

类似于 io.input,但操作在默认输出文件上。

file:read (...)

按指定的格式读取一个文件。按每个格式将返回一个字符串或数字, 如果不能正确读取将返回 nil,若没有指定格式将指默认按行方式进行读取。格式:

格式 含义
"*n" 读取一个数字
"*a" 从当前位置读取整个文件。若当前位置为文件尾,则返回空字符串
"*l" 读取下一行的内容。若为文件尾,则返回nil。(默认)
number 读取指定字节数的字符。若为文件尾,则返回nil。如果number为0,则返回空字符串,若为文件尾,则返回nil

io.read (...)

相当于 io.input():read

io.type (obj)

检测 obj 是否一个可用的文件句柄。如果 obj 是一个打开的文件句柄,则返回 "file" 如果 obj 是一个已关闭的文件句柄,则返回 "closed file" 如果 obj 不是一个文件句柄,则返回 nil。

file:write (...)

把每一个参数的值写入文件。参数必须为字符串或数字,若要输出其它值,则需通过 tostring 或 string.format 进行转换。

io.write (...)

相当于 io.output():write。

file:seek ([whence] [, offset])

设置和获取当前文件位置,成功则返回最终的文件位置(按字节,相对于文件开头),失败则返回 nil 加错误信息。缺省时,whence 默认为 "cur",offset 默认为 0 。 参数 whence:

whence 含义
"set" 文件开始
"cur" 文件当前位置(默认)
"end" 文件结束

file:setvbuf (mode [, size])

设置输出文件的缓冲模式。模式:

模式 含义
"no" 没有缓冲,即直接输出
"full" 全缓冲,即当缓冲满后才进行输出操作(也可调用flush马上输出)
"line" 以行为单位,进行输出

最后两种模式,size 可以指定缓冲的大小(按字节),忽略 size 将自动调整为最佳的大小。

高级部分

Lua 元表(Metatable)

在 Lua table 中我们可以访问对应的 key 来得到 value 值,但是却无法对两个 table 进行操作(比如相加)。

因此 Lua 提供了元表(Metatable),允许我们改变 table 的行为,每个行为关联了对应的元方法。

例如,使用元表我们可以定义 Lua 如何计算两个 table 的相加操作 a+b。

当 Lua 试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫 __add 的字段,若找到,则调用对应的值。 __add 等即时字段,其对应的值(往往是一个函数或是 table)就是"元方法"。

有两个很重要的函数来处理元表:

  • setmetatable(table,metatable): 对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。
  • getmetatable(table): 返回对象的元表(metatable)。

以下实例演示了如何对指定的表设置元表:

mytable = {}              *-- 普通表*
mymetatable = {}            *-- 元表*
setmetatable(mytable,mymetatable)   *-- 把 mymetatable 设为 mytable 的元表*

以上代码也可以直接写成一行:

mytable = setmetatable({},{})

以下为返回对象元表:

getmetatable(mytable)                 -- 这会返回 mymetatable

__index 元方法

这是 metatable 最常用的键。

当你通过键来访问 table 的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index 键。如果__index包含一个表格,Lua会在表格中查找相应的键。

我们可以在使用 lua 命令进入交互模式查看:

$ 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 个步骤:

- 1.在表中查找,如果找到,返回该元素,找不到则继续
- 2.判断该表是否有元表,如果没有元表,返回 nil,有元表则继续。
- 3.判断元表有没有 __index 方法,如果 __index 方法为 nil,则返回 nil;如果 __index 方法是一个表,则重复 1、2、3;如果 __index 方法是一个函数,则返回该函数的返回值。

该部分内容来自作者寰子:https://blog.csdn.net/xocoder/article/details/9028347

__newindex 元方法

__newindex 元方法用来对表更新,__index则用来对表访问 。

当你给表的一个缺少的索引赋值,解释器就会查找__newindex 元方法:如果存在则调用这个函数而不进行赋值操作。

以下实例演示了 __newindex 元方法的应用:

mymetatable = {}
mytable = setmetatable({key1 = "value1"}, { __newindex = mymetatable })

print(mytable.key1)

mytable.newkey = "新值2"
print(mytable.newkey,mymetatable.newkey)

mytable.key1 = "新值1"
print(mytable.key1,mymetatable.key1)

以上实例执行输出结果为:

value1
nil    新值2
新值1    nil

以上实例中表设置了元方法 __newindex,在对新索引键(newkey)赋值时(mytable.newkey = "新值2"),会调用元方法,而不进行赋值。而如果对已存在的索引键(key1),则会进行赋值,而不调用元方法 __newindex。

以下实例使用了 rawset 函数来更新表:

mytable = setmetatable({key1 = "value1"}, {
    __newindex = function(mytable, key, value)
        rawset(mytable, key, "\""..value.."\"")
    end
})

mytable.key1 = "new value"
mytable.key2 = 4

print(mytable.key1,mytable.key2)

以上实例执行输出结果为:

new value    "4"

为表添加操作符

以下实例演示了两表相加操作:

-- 计算表中最大值,table.maxn在Lua5.2以上版本中已无法使用
-- 自定义计算表中最大键值函数 table_maxn,即返回表最大键值
function table_maxn(t)
    local mn = 0
    for k, v in pairs(t) do
        if mn < k then
            mn = k
        end
    end
    return mn
end

-- 两表相加操作
mytable = setmetatable({ 1, 2, 3 }, {
  __add = function(mytable, newtable)
    for i = 1, table_maxn(newtable) do
      table.insert(mytable, table_maxn(mytable)+1,newtable[i])
    end
    return mytable
  end
})

secondtable = {4,5,6}

mytable = mytable + secondtable
        for k,v in ipairs(mytable) do
print(k,v)
end

以上实例执行输出结果为:

1    1
2    2
3    3
4    4
5    5
6    6

add 键包含在元表中,并进行相加操作。 表中对应的操作列表如下:(**注意:******是两个下划线)

模式 描述
__add 对应的运算符 '+'.
__sub 对应的运算符 '-'.
__mul 对应的运算符 '*'.
__div 对应的运算符 '/'.
__mod 对应的运算符 '%'.
__unm 对应的运算符 '-'.
__concat 对应的运算符 '..'.
__eq 对应的运算符 '=='.
__lt 对应的运算符 '<'.
__le 对应的运算符 '<='.

__call 元方法

__call 元方法在 Lua 调用一个值时调用。以下实例演示了计算表中元素的和:

-- 计算表中最大值,table.maxn在Lua5.2以上版本中已无法使用
-- 自定义计算表中最大键值函数 table_maxn,即计算表的元素个数
function table_maxn(t)
    local mn = 0
    for k, v in pairs(t) do
        if mn < k then
            mn = k
        end
    end
    return mn
end

-- 定义元方法__call
mytable = setmetatable({10}, {
  __call = function(mytable, newtable)
        sum = 0
        for i = 1, table_maxn(mytable) do
                sum = sum + mytable[i]
        end
    for i = 1, table_maxn(newtable) do
                sum = sum + newtable[i]
        end
        return sum
  end
})
newtable = {10,20,30}
print(mytable(newtable))

以上实例执行输出结果为:

70

__tostring 元方法

__tostring 元方法用于修改表的输出行为。以下实例我们自定义了表的输出内容:

mytable = setmetatable({ 10, 20, 30 }, {
  __tostring = function(mytable)
    sum = 0
    for k, v in pairs(mytable) do
                sum = sum + v
        end
    return "表所有元素的和为 " .. sum
  end
})
print(mytable)

以上实例执行输出结果为:

表所有元素的和为 60

从本文中我们可以知道元表可以很好的简化我们的代码功能,所以了解 Lua 的元表,可以让我们写出更加简单优秀的 Lua 代码。

Lua发起网络请求

local http = require "resty.http"

Post请求

function _M.send(appid, body)
    local httpc = http.new()
    httpc:set_timeouts(5000, timeout, timeout)

    local rsp, err = httpc:request_uri(url, {
        method = "POST",
        body = body,
        headers = {
            ["Content-Type"] = "application/json",
            ["appId"] = appid, 
            ["api-key"] = appid,
            ["X-AI-GATEWAY-APP-ID"] = ngx.ctx.GateWayId
        }
    })

    local code, ret , httpcode, rsp_obj, rsp_body = 0, nil, 200, {}, nil 

    while true do 
        if err then  
            code, ret = 30001, err  
            break 
        end 
 
        if not rsp then  
            code, ret = 30002, "send to chatgpt server failed"
            break 
        end 

        if rsp.status ~= 200 then  
            code, ret = 30003, "send to chatgpt server failed, http error code: " .. tostring(rsp.status)
            break 
        end 

        break 
    end 

    if rsp and rsp.status then 
        httpcode = rsp.status
    end 

    if rsp and rsp.body then  
        local tmp_obj = cjson.decode(rsp.body) 
        if tmp_obj then  
            rsp_obj = tmp_obj
        end  
     
        rsp_body = rsp.body 
    end 

    return rsp_obj, rsp_body, code, httpcode, ret 
end

SSE

resty.sse 是与 OpenResty 相关的一个模块,用于实现服务器发送事件(Server-Sent Events,SSE)。SSE 是一种用于在浏览器和服务器之间建立持久连接的技术,允许服务器向客户端推送数据。通过 resty.sse 模块,可以在 OpenResty 中实现代理 SSE 请求,从而实现服务器向浏览器推送信息。

local sse = require "resty.sse"

function _M.send(appid, body)   
    local code, ret , httpcode, rsp_obj, rsp_body = 0, nil, 200, {}, nil 

    local conn, err = sse.new()

    if not conn then
        return conn, rsp_obj, rsp_body, code, httpcode, ret, "failed to get connection: " .. err 
    end

    conn:set_timeouts(5000, timeout, timeout) 

    local rsp, err, flag = conn:request_uri(url, {
        method = "POST",
        body = body,
        headers = {
            ["Content-Type"] = "application/json;charset=utf8", 
            ["appId"] = appid, 
            ["api-key"] = appid, 
            ["X-AI-GATEWAY-APP-ID"] = ngx.ctx.GateWayId 
        }
    })

   while true do  
        if not conn then  
            code, ret = 40001, err 
            break 
        end 

        if not rsp then  
            code, ret = 40002, err
            break 
        end 

        if rsp.status ~= 200 then
            code, ret = 40003, "send to chatgpt server failed, http error code: " .. tostring(rsp.status)
            break
        end 

        break
    end 

    conn:transfer_encoding_is_chunked() 

    if rsp and rsp.status then 
        httpcode = rsp.status 
       
    end 

    if rsp and rsp.body then  
        local tmp_obj = cjson.decode(rsp.body) 
        if tmp_obj then  
            rsp_obj = tmp_obj
        end  

        rsp_body = rsp.body
    end

    return conn, rsp_obj, rsp_body, code, httpcode, ret
end 

错误处理

语法错误

语法错误通常是由于程序的组件(如运算符,表达式)使用不当引起的

-- test.lua 文件
a == 2
-- lua: test.lua:2: syntax error near '=='

运行错误

运行错误是程序可以正常执行,但是输出报错信息。

function add(a,b)
   return a+b
end

add(10)

错误处理

我们可以使用两个函数: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位置(文件+行号)
  • Level=2:指出哪个调用error的函数的函数
  • Level=0:不添加错误位置信息

pcall 和 xpcall、debug

Lua中处理错误,可以使用函数pcall(protected 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..

Lua 的Nginx模块的API

  1. ngx_log = ngx.log - 用于记录日志信息
  2. ngx_ERR = ngx.ERR - 表示错误级别的日志
  3. ngx_INFO = ngx.INFO - 表示信息级别的日志
  4. ngx_now = ngx.now - 获取当前时间戳(以秒为单位)
  5. ngx_time = ngx.time - 获取当前时间的秒数
  6. ngx_exit = ngx.exit - 终止请求处理并返回指定的状态码
  7. ngx_location_capture = ngx.location.capture - 捕获其他location的响应
  8. ngx_decode_base64 = ngx.decode_base64 - 对Base64编码的字符串进行解码
  9. ngx_encode_base64 = ngx.encode_base64 - 对字符串进行Base64编码
  10. ngx_hmac_sha1 = ngx.hmac_sha1 - 计算HMAC-SHA1哈希值
  11. ngx_HTTP_POST = ngx.HTTP_POST - 表示HTTP POST请求
  12. ngx_HTTP_OK = ngx.HTTP_OK - 表示HTTP响应码200 OK
  13. ngx_http_time = ngx.http_time - 将时间戳转换为HTTP日期格式
  14. ngx_req_set_header = ngx.req.set_header - 设置请求头信息
  15. ngx_crc32_short = ngx.crc32_short - 计算字符串的CRC32校验值
  16. ngx_gsub = ngx.re.gsub - 用于在字符串中进行全局替换
  17. ngx_re_gmatch = ngx.re.gmatch - 用于在字符串中进行全局匹配
  18. ngx_null = ngx.null - 表示Nginx的null值
  19. ngx_sub = ngx.re.sub - 用于在字符串中进行替换操作
  20. ngx.req.get_method(): 获取当前请求的HTTP方法,例如GET、POST、PUT等。
  21. ngx.req.get_uri_args(): 获取当前请求的URI参数(即查询字符串参数)。
  22. ngx.req.get_headers(): 获取当前请求的HTTP头部信息。
  23. ngx.req.read_body(): 读取请求体内容。如果请求方法是POST或PUT,需要先调用这个方法来读取请求体。
  24. ngx.req.get_body_data(): 获取请求体的内容。
  25. ngx.req.set_header(name, value): 设置指定的HTTP头部信息。
  26. ngx.req.clear_header(name): 清除指定的HTTP头部信息。
  27. ngx.req 获取请求信息
posted @ 2025-04-07 22:13  小郑[努力版]  阅读(55)  评论(0)    收藏  举报