Nginx/OpenResty详解,Nginx Lua编程,Lua开发基础

Lua开发基础

Lua是一个可扩展的轻量级脚本语言,Lua的设计目是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua的代码简洁优美,几乎在所有操作系统和平台上都可以编译和运行。

Lua脚本需要通过Lua解释器来解释执行,除了Lua官方的默认解释器外,目前使用广泛的Lua解释器叫作LuaJIT。

LuaJIT是采用C语言编写的Lua脚本解释器。LuaJIT被设计成全兼容标准Lua 5.1,因此LuaJIT代码的语法和标准Lua的语法没多大区别。LuaJIT和Lua的一个区别是,LuaJIT的运行速度比标准Lua快数十倍,可以说是一个Lua的高效率版本。

 Lua模块的定义和使用

与Java类似,实际开发的Lua代码需要进行分模块开发。Lua中的一个模块对应一个Lua脚本文件。使用require指令导入Lua模块,第一次导入模块后,所有Nginx进程全局共享模块的数据和代码,每个Worker进程需要时会得到此模块的一个副本,不需要重复导入,从而提高Lua应用的性能。接下来,演示开发一个简单的Lua模块,用来存放公有的基础对象和基础函数。

//代码清单:src/luaScript/module/common/basic.lua
--定义一个应用程序公有的Lua对象app_info
local app_info = { version = "0.10" }
--增加一个path属性,保存Nginx进程所保存的Lua模块路径,包括conf文件配置的部分路径
app_info.path = package.path;
--局部函数,取得最大值
local function max(num1, num2)
 if (num1 > num2) then
 result = num1;
 else
 result = num2;
 end
 return result;
end
--统一的模块对象
local _Module = {
 app_info = app_info;
 max = max;
}
return _Module

模块内的所有对象、数据、函数都定义成局部变量或者局部函数。然后,对于需要暴露给外部的对象或者函数,作为成员属性保存到一个统一的Lua局部对象(如_Module)中,通过返回这个统一的局部对象将内部的成员对象或者方法暴露出去,从而实现Lua的模块化封装。

 Lua模块的使用

接下来,创建一个Lua脚本
src/luaScript/module/demo/helloworld.lua来调用前面定义的这个基础模块src/luaScript/module/common/basic.lua文件。

helloworld.lua的代码如下:

//代码清单:src/luaScript/module/demo/helloworld.lua
---启动调试
local mobdebug = require("luaScript.initial.mobdebug");
mobdebug.start();
--导入自定义的模块
local basic = require("luaScript.module.common.basic ");
--使用模块的成员属性
ngx.say("Lua path is: " .. basic.app_info.path);
ngx.say("<br>" );
--使用模块的成员方法
ngx.say("max 1 and 11 is: ".. basic.max(1,11) );

在使用require内置函数导入Lua模块时,对于多级目录下的模块,使用require("目录1.目录2.模块名")的形式进行加载,源目录之间的“/”斜杠分隔符改成“.”点号分隔符。这一点和Java的包名的分隔符类似。

--导入自定义的模块
local basic = require("luaScript.module.common.basic");

Lua文件查找时,首先会在Nginx的当前工作目录查找,如果没有

找到,就会在Nginx的Lua包路径lua_package_path和lua_package_cpath声明的位置查找。整个Lua文件的查找过程和Java的.class文件查找的过程很类似。需要注意的是,Lua包路径需要在nginx.conf配置文件中进行配置:

 lua_package_path " E:/tool/ZeroBraneStudio-1.80/lualibs/?/?.lua;;";
 lua_package_cpath " E:/tool/ZeroBraneStudio-1.80/bin/clibs/?.dll;;";

这里有两个包路径配置项:lua_package_path用于配置Lua文件的包路径;lua_package_cpath用于配置C语言模块文件的包路径。在Linux系统上,C语言模块文件的类型是“.so”;在Windows平台上,C语言模块文件的类型是“.dll”。

Lua包路径如果需要配置多个路径,那么路径之间使用分号“;”分隔。末尾的两个分号“;;”表示加上Nginx默认的Lua包搜索路径,其中包含Nginx的安装目录下的lua目录。

//路径清单:一个默认的Lua文件搜索路径输出案例
./site/lualib/?.ljbc;
./site/lualib/?/init.ljbc;
./lualib/?.ljbc;
./lualib/?/init.ljbc;
./site/lualib/?.lua;
./site/lualib/?/init.lua;
./lualib/?.lua;
./lualib/?/init.lua;
.\?.lua;
E:\tool\openresty-1.13.6.2-win32\lualib\?.lua;
E:\tool\openresty-1.13.6.2-win32\lua\?.lua;
E:\tool\openresty-1.13.6.2-win32\lua\?\init.lua;;

在OpenResty的lualib下已经提供了大量第三方开发库,如CJSON、Redis客户端、MySQL客户端等,并且这些Lua模块已经包含到默认的搜索路径中。OpenResty的lualib下的模块可以直接在Lua文件中通过require方式导入:

导入
操作模块--导入redis操作模块
local redis = require("resty.redis")
--导入cjson操作模块
local cjson = require("cjson")

 Lua的数据类型

Lua中大致有8种数据类型,具体如表8-1所示。

表8-1 8种数据类型

Lua是弱类型语言,和JavaScript等脚本语言类似,变量没有固定的数据类型,每个变量可以包含任意类型的值。使用内置的type(…)方法可以获取该变量的数据类型。下面是一段简单的类型输出演示程序。

--输出数据类型
local function showDataType()
 local i;
 basic.log("字符串的类型", type("hello world"))
 basic.log("方法的类型", type(showDataType))
 basic.log("true的类型", type(true))
 basic.log("整数数字的类型", type(360))
 basic.log("浮点数字的类型", type(360.0))
 basic.log("nil值的类型", type(nil))
 basic.log("未赋值变量i的类型", type(i))

end上面的方法定义在
luaScript.module.demo.dataType模块中。然后定义一个专门的调试模块runDemo,来调用上面定义的showDataType方法。runDemo.lua的代码清单如下:

---启动调试
local mobdebug = require("luaScript.initial.mobdebug");
mobdebug.start();
--导入自定义的基础模块
local basic = require("luaScript.module.common.basic");
--导入自定义的dataType模块
local dataType = require("luaScript.module.demo.dataType");
ngx.say("下面是数据类型演示的结果输出:<br>" );
dataType.showDataType();

在nginx-debug.conf配置好runDemo.lua之后,就可以通过浏览器执行了,输出的结果如图8-3所示。

图8-3 Lua数据类型输出

关于Lua的数据类型,有以下几点需要注意:

(1)nil是一种类型,在Lua中表示“无效值”。nil也是一个值,表示变量是否被赋值,如果变量没有被赋值,那么值为nil,类型也为nil。

与Nginx略微有一点不同,OpenResty还提供了一种特殊的空值,即ngx.null,用来表示空值,但是不同于nil。

(2)boolean(布尔类型)的可选值为true和false。在Lua中,只有nil与false为“假”,其他所有值均为“真”,比如数字0和空字符串都是“真”。这一点和Java语言的boolean类型还是有一点区别的。

(3)number类型用于表示实数,与Java中的double类型类似。但是又有区别,Lua的整数类型也是number。一般来说,Lua中的number类型是用双精度浮点数来实现的。可以使用数学函数math.lua来操作number类型的变量。在math.lua模块中定义了大量数字操作方法,比如定义了floor(向下取整)和ceil(向上取整)等操作。下面是一个演示方法。

--演示取整操作
local function intPart(number)
 basic.log("演示的整数",number)
 basic.log("向下取整是", math.floor(number));
 basic.log("向上取整是", math.ceil(number))
end

上面的方法定义在
luaScript.module.demo.dataType模块中,然后在runDemo.lua模块中调用上面定义的intPart方法。调用的代码清单如下:

---启动调试
local mobdebug = require("luaScript.initial.mobdebug");
mobdebug.start();
--导入自定义的dataType模块
local dataType = require("luaScript.module.demo.dataType");
ngx.say("<hr>下面是数字取整的输出:<br>" );dataType.intPart(0.01);
dataType.intPart(3.14);

运行之后,输出的结果如图8-4所示。

图8-4 floor(向下取整)和ceil(向上取整)操作的结果

(4)table类型实现了一种抽象的“关联数组”,相当于Java中的Map。“关联数组”是一种具有特殊索引方式的数组,索引(也就是Map中的key)通常是number类型或者string类型,也可以是除nil以外的任意类型的值。默认情况下,table中的key是number类型的,并且key的值为递增的数字。

(5)和JavaScript脚本语言类似,在Lua中的函数也是一种数据类型,类型为function。函数可以存储在变量中,可以作为参数传递给其他函数,还可以作为其他函数的返回值。定义一个有名字的函数本质上是定义一个函数对象,然后赋值给变量名称(函数名)。例如,前面定义在basic.lua中的max函数可以变成如下形式:

--局部函数,取得最大值
local max= function (num1, num2)
 local result = nil;
 if (num1 > num2) then
 result = num1;
 else
 result = num2;
 end
 return result;
end

在上面的代码中,首先定义了一个变量max,然后使用function定义了一个匿名函数,并且将函数赋给max变量。这种定义方法等价于直接定义一个带名字的函数的方式:

--局部函数,取得最大值
local max= function (num1, num2)
 //省略函数体
end

 Lua的字符串

Lua中有3种方式表示字符串:

(1)使用一对匹配的半角英文单引号,例如'hello';

(2)使用一对匹配的半角英文双引号,例如"hello";

(3)使用一种双方括号“[[]]”括起来的方式定义,例如[["add\name",'hello']]。需要说明的是,双方括号内的任何转义字符不被处理,比如"\n"就不会被转义。

Lua的字符串的值是不可改变的,和Java一样,string类型是不可变类型。如果需要改变,就需要根据修改要求来创建一个新的字符串并返回。另外,Lua不支持通过下标来访问字符串的某个字符。

Lua定义了一个负责字符串操作的string模块,包含很多强大的字符操作函数。主要的字符串操作介绍如下:

(1)“..”:字符串拼接符号。

在Lua中,如果需要进行字符串的连接,使用两点符号“..”,例如:

--演示字符串操作
local function stringOperator(s)
 local here="这里是:" .. "高性能研习社群" .. "疯狂创客圈";
 print(here);
 basic.log("字符串拼接演示",here);
 end

(2)string.len(s):获取字符串的长度。

此函数接收一个字符串作为参数,返回它的长度。此函数的功能和#运算符类似,后者也是取字符串的长度。在实际开发过程中,建议尽量使用#运算符来获取Lua字符串的长度。

--演示字符串的长度获取
local function stringOperator(s)
 local here = "这里是:" .. "高性能研习社群" .. "疯狂创客圈";
 basic.log("获取字符串的长度", string.len(here));
 basic.log("获取字符串的长度方式二", #here);end

(3)string.format(formatString,...):格式化字符串。

第一个参数formatString表示需要进行格式化的字符串规则,通常由常规文本和格式指令组成,比如:

--简单的圆周率格式化规则
string.format(" 保留两位小数的圆周率 %.4f", 3.1415926);
--格式化日期
string.format("%s %02d-%02d-%02d", "今天is:", 2020, 1, 1));

在formatString参数中,除了常规文本之外,还有格式指令。格式指令由%加上一个类型字母组成,比如%s(字符串格式化)、%d(整数格式化)、%f(浮点数格式化)等,在%和类型符号的中间可以选择性地加上一些格式控制数据,比如%02d,表示进行两位的整数格式输出。总体来说,formatString参数中的格式化指令规则与标准C语言中printf函数的格式化规则基本相同。

format函数后面的参数是一个可变长参数,表示一系列需要进行格式化的值。一般来说,前面的formatString参数中有多少格式化指令,后面就可以放置对应数量的参数值,并且后面的参数类型需要与formatString参数中对应位置的格式化指令中的类型符号相匹配。

(4)string.find(s,pattern[,init[,plain]]):字符串匹配。在s字符串中查找第一个匹配正则表达式pattern的子字符串,返回第一次在s中出现的满足条件的子串的开始位置和结束位置,若匹配失败,则返回nil。第三个参数init默认为1,表示从起始位置1开始找起。第四个参数的值默认为false,表示第二个参数pattern为正则表达式,默认进行表达式匹配,当第四个参数为true时,只会把pattern看成一个普通字符串。

--演示字符串查找操作
local function stringOperator(s)
 local here="这里是:" .. "高性能研习社群" .. "疯狂创客圈";
 local find = string.find;
 basic.log("字符串查找",find(here,"疯狂创客圈"));
end

(5)string.upper(s):字符串转成大写接收一个字符串s,返回一个把所有小写字母变成大写字母的字符串。

--演示字符串操作
local function stringOperator(s)
 local src = "Hello world!";
 basic.log("字符串转成大写",string.upper(src));
 basic.log("字符串转成小写",string.lower(src));
end

与string.upper(s)方法类似,string.lower(s)方法的作用是接收一个字符串s,返回一个全部字母变成小写的字符串。

 Lua的数组容器

Lua数组的类型定义的关键词为table,通过名字进行翻译的话,可以直接翻译为二维表。和Java的数组对比起来,Lua数组有以下几个要点:

要点一:Lua数组内部实际采用哈希表保存键-值对,这一点和Java的容器HashMap类似。不同的是,Lua在初始化一个普通数组时,如果不显式地指定元素的key,就会默认用数字索引作为key。

要点二:定义一个数组使用花括号,中间加上初始化的元素序列,元素之间以逗号隔开即可。

--定义一个数组
local array1 = { "这里是:" , "高性能研习社群" ,"疯狂创客圈" }
--定义一个元素类型为键-值对的数组,相当于Java的HashMap
local array2 = { k1="这里是:" , k2= "高性能研习社群" , k3="疯狂创客圈" }

要点三:普通Lua数组的数字索引对应于Java的元素下标,是从1开始计数的。

要点四:普通Lua数组的长度的计算方式和C语言有些类似。从第一个元素开始,计算到最后一个非nil的元素为止,中间的元素数量就是长度。

要点五:取得数组元素值使用[]符号,形式为array[key],其中array代表数组变量名称,key代表元素的索引,这一点和Java语言类似。对于普通的数组,key为元素的索引值;对于键-值对(Key-ValuePair)类型的数组容器,key就是键-值对中的key。

迭代上面定义的普通数组 --迭代上面定义的普通数组

 for i = 1, 3 do
 ngx.say(i .. "=" .. array1[i] .. ",");
 end
 ngx.say("<br>");
 --迭代上面定义的键-值对的容器数组
 for k, v in pairs(array2) do
 ngx.say(k .. "=" .. array2[k] .. ",");
 end
 ngx.say("<br><br>");

Lua定义了一个负责数组和容器操作的table模块,主要的字符串操作大致如下:

(1)table.getn(t):获取长度。

对于普通的数组,键从1到n放着一些非空值时,它的长度就精确为n。如果数组有一个元素为“空值”(nil值被夹在中间,相当于有一个空洞),那么数组长度为“空值”前面部分的数组长度,“空值”后面的数组元素不会计算在内。

获取数组长度,Lua中还有一个更为简单的操作符,即一元操作符#。并且,在Lua 5.1之后的版本去掉了table.getn(t)方法,直接使用#获取长度。

 --定义一个数组
 local array1 = { "这里是:", "高性能研习社群", "疯狂创客圈" }
 --定义一个K-V元素类型的数组
 local array2 = { k1 = "这里是:", k2 = "高性能研习社群", k3 = "疯狂创客圈" }
 --取得数组长度
 basic.log("使用table.getn (t)获取长度", table.getn (array1));
 basic.log("使用 一元操作符#获取长度", #array1 );

(2)table.concat(array,[,sep,[,i,[,j]]]):连接数组元素。

按照array[i]..sep..array[i+1]..sep..array[j]的方式将普通数组中所有的元素连接成一个字符串并返回。分隔字符串sep默认为空白字符串。起始位置i默认为1,结束位置j默认是array的长度。如果i大于j,就返回一个空字符串。

 local testTab = { 1, 2, 3, 4, 5, 6, 7 }
 basic.log("连接元素",table.concat(testTab)) --输出: 1234567
 basic.log("带分隔符连接元素",table.concat(testTab, "*", 1, 3)) --输出: 1*2*3(3)table.insert(array,[pos,],value):插入元素。

在array的位置pos处插入元素value,后面的元素向后顺移。pos的默认值为#list+1,因此调用table.insert(array,x)会将x插在普通数组array的末尾。

 local testTab = { 1, 2, 3, 4 }
 --插入一个元素到末尾
 table.insert(testTab, 5)
 basic.printTable(testTab) --输出: 1 2 3 4 5
 --插入一个元素到位置索引2
 table.insert(testTab, 2, 10)
 basic.printTable(testTab) --输出: 1 10 2 3 4 5

上面用了一个新的成员basic.printTable(testTab),是为了输出数组元素。在basic模块专门定义了一个新输出方法_printTable(tab),然后暴露为printTable,代码如下:

--在屏幕上输出table元素
function _printTable(tab)
 local output = ""
 for i, v in ipairs(tab) do
 ngx.say(v .. " ");
 end
 ngx.say("<br>");
end

(4)table.remove(array[,pos]):删除元素。删除array中pos位置上的元素,并返回这个被删除的值。当pos是1到#list之间的整数时,将后面的所有元素前移一位,并删除最后一个元素。

 testTab = { 1, 2, 3, 4, 5, 6, 7 }
 --删除最后一个元素
 table.remove(testTab)
 basic.printTable(testTab) --输出: 1 2 3 4 5 6
 --删除第二个元素
 table.remove(testTab, 2) --输出: 1 3 4 5 6
 basic.printTable(testTab)

 Lua的控制结构

首先介绍分支控制结构if-else。if-else是Java工程师熟知的一种控制结构,分成3类进行介绍:单分支结构、两分支结构和多分支结构。

1.单分支结构:if

以关键词if开头,以关键词end结束。这一点和Java不同,Java中

使用右花括号作为分支结构体的结束符号。

 --单分支结构
 Local x = '疯狂创客圈'
 if x == '疯狂创客圈' then
 basic.log("单分支演示:", "这个是一个高性能研习社群")
 end

输出的结果是:

单分支演示:这个是一个高性能研习社群

2.两分支结构:if-else

与Java类似,两分支结构的控制语句在单分支的基础上加入了else子句。

 --两分支
 local x = '疯狂创客圈'
 if x == '这个是一个高性能研习社群' then
 basic.log("两分支演示:", "这儿是疯狂创客圈")
 else
两分支演示
这儿还是疯狂创客圈 basic.log("两分支演示:", "这儿还是疯狂创客圈")
 end

输出的结果是:

两分支演示:这儿还是疯狂创客圈

3.多分支结构:if-elseif-else

多分支结构就是添加elseif条件子句,可以添加多个elseif条件子句。与Java语言不同的是,else与if不是分开的,是连在一起的。

 --多分支
local x = '疯狂创客圈' if x == '这个是一个高性能研习社群' then
 basic.log("多分支演示:", "这儿是疯狂创客圈")
 elseif x == '疯狂创客圈' then
 basic.log("多分支演示:", "这个是一个高性能研习社群")
 else
 basic.log("多分支演示:", "这儿不是疯狂创客圈")
 end

输出的结果是:

多分支演示:这个是一个高性能研习社群然后介绍for循环控制结构,分成两类进行介绍:基础for循环和增强版的foreach循环。

(1)基础for循环,语法如下:

for var = begin, finish, step do
 --body
end

基础for循环的语法中,var表示迭代变量,begin、finish、step表示控制的变量。迭代变量var从begin开始,一直变化到finish循环结束,每次变化都以step作为步长递增。begin、finish、step可以是表达式,但是3个表达式只会在循环开始时执行一次。其中,步长表达式step是可选的,如果没有设置,默认值就为1。迭代变量var的作用域仅在for循环内,并且在循环过程中不要改变迭代变量var的值,否则会带来不可预知的影响。

--for循环,步长为2
for i = 1, 5, 2 do
 ngx.say(i .. " ")
end
 --for循环,步长为1
 ngx.say("<br>");
 for i = 1, 5 do
 ngx.say(i .. " ")
 end

输出的结果分别为:

 1 3 5
 1 2 3 4 5

(2)增强版的foreach循环,语法如下:

for key, value in pairs(table) do
 --body
end

前面讲到,在Lua的table内部保存有一个键-值对的列表,foreach循环就是对这个列表中的键-值对进行迭代,pairs(table)函数的作用就是取得table内部的键-值对列表。

--foreach循环,打印table t中所有的键(key)和值(value)
 local days = {
 "Sunday", "Monday", "Tuesday", "Wednesday",
 "Thursday", "Friday", "Saturday"
 }
 for key, value in pairs(days) do
 ngx.say(key .. ":" .. value .. "; ")
 end
 local days2 = {
 Sunday = 1, Monday = 2, Tuesday = 3, Wednesday = 4,
 Thursday = 5, Friday = 6, Saturday = 7
 }
 for key, value in pairs(days2) do
 ngx.say(key .. ":" .. value .. "; ")
end

输出的结果如下:

1:Sunday; 2:Monday; 3:Tuesday; 4:Wednesday; 5:Thursday; 6:Friday; 7:Saturday;
Tuesday:3; Monday:2; Sunday:1; Thursday:5; Friday:6; Wednesday:4; Saturday:7;

 Lua的函数定义

Lua函数使用关键词function来定义,使用函数的好处如下:

(1)降低程序的复杂性:模块化编程的好处是将复杂问题变成一个个小问题,然后分而治之。把函数作为一个独立的模块或者当作一个黑盒,而不需要考虑函数里面的细节。

(2)增强代码的复用度:当程序中有相同的代码部分时,可以把这部分写成一个函数,通过调用函数来实现这部分代码的功能,可以节约空间、减少代码长度。

(3)隐含局部变量:在函数中使用局部变量,变量的作用范围不会超出函数,这样就不会给外界带来干扰。

首先来看Lua的函数定义,格式如下:

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

对上面定义的参数说明如下:

(1)optional_function_scope:该参数表示所定义的函数是全局函数还是局部函数,该参数是可选参数,默认为全局函数,如果定义为局部函数,那么设置为关键字local。

(2)function_name:该参数用于指定函数名称。

(3)argument1,argument2,argument3,…,argumentn:函数参数,多个参数以逗号隔开,也可以不带参数。

(4)function_body:函数体,函数中需要执行的代码语句块。

(5)
result_params_comma_separated:函数返回值,Lua语言中的函数可以返回多个值,每个值以逗号隔开。

下面定义一个局部函数max(),参数为num1和num2,用于比较两个值的大小,并返回最大值:

--局部函数,取得最大值
local function max(num1, num2)
 local result = nil;
 if (num1 > num2) then
 result = num1;
 else
 result = num2;
 end
 return result;
end

怎么使用Lua的可变参数呢?和Java语言类似,Lua语言中的函数可以接收可变数目的参数,在函数参数列表中使用三点“...”表示函数有可变的参数。

在函数的内部可以通过一个数组访问可变参数的实参列表,简称可变实参数组。只不过访问可变实参数组前需要将其赋值给一个变量。

--在屏幕上打印日志,可以输入多个打印的数据
local function log(...)
 local args = { ... } --这里的...和{}符号中间需要有空格号,否则会出错
 for i, v in pairs(args) do
 print("index:", i, " value:", v)
 ngx.say(v .. ",");
 end
 ngx.say("<br>");
end

这里不得不提函数参数值的传递方式。大家知道,主要有两种方式:一种是值传递;另一种是引用传递。Lua中的函数的参数大部分是按值传递的。值传递就是调用函数时,把实参的值通过赋值传递给形参,然后形参的改变和实参就没有关系了。在这个过程中,实参和形参是通过在参数表中的位置匹配起来的。但是有一种数据类型除外,就是table数组类型,table类型的传递方式是引用传递。当函数参数是table类型时,传递进来的是实际参数的引用(内存地址),此时在函数内部对该table所做的修改会直接对实际参数生效,而无须自己返回结果和让调用者进行赋值。怎么使得函数可以有多个返回值呢?Lua语言具有一项与众不同的特性,允许函数返回多个值。比如,Lua的内置函数string.find,在源字符串中查找目标字符串,若查找成功,则返回两个值:一个起始位置和一个结束位置。

local s, e = string.find("hello world", "lo") -->返回值为:4 5
print(s, e) -->输出:4 5

如果一个函数需要在return后面返回多个值,那么值与值之间用“,”隔开。

最后总结一下定义一个Lua的函数要注意的几点:

(1)利用名字来解释函数、变量的目的是使人通过名字就能看出来函数的作用。让代码自己说话,不需要注释最好。

(2)由于全局变量一般会占用全局名字空间,同时也有性能损耗(查询全局环境表的开销),因此我们应当尽量使用“局部函数”,在开头加上local修饰符。

(3)由于函数定义本质上就是变量赋值,而变量的定义总是要放置在变量使用之前,因此函数的定义也需要放置在函数调用之前。

Lua的面向对象编程

大家知道,在Lua中使用表(table)实现面向对象,一个表就是一个对象。由于Lua的函数(function)也是一种数据类型,表可以拥有前面介绍的8大数据类型的成员属性。

下面在DataType.lua模块中定义带有一个成员的_Square类,代码如下:

--正方形类
_Square = { side = 0 }
_Square.__index = _Square
--类的方法getArea
function _Square.getArea(self)
 return self.side *self.side;
end
--类的方法new
function _Square.new(self, side)
 local cls = {}
 setmetatable(cls, self)
 cls.side = side or 0
 return cls
end
--一个统一的模块对象
local _Module = {
 ...
 Square = _Square;
}

在调用Square类的方法时,建议将点号改为冒号。使用冒号进行成员方法调用时,Lua会隐性传递一个self参数,它将调用者对象本身作为第一个参数传递进来。

ngx.say("<br><hr>下面是面向对象操作的演示:<br>");
local Square = dataType.Square;
local square = Square:new(20);
ngx.say("正方形的面积为", square:getArea());

输出的结果如下:

下面是面向对象操作的演示:

正方形的面积为400

Lua的面向对象用到了两个重要的概念:

(1)metatable元表:简单来说,如果一个表(也叫对象)的属性找不到,就去它的元表中查找。通过setmetatable(table,metatable)方法设置一个表的元表。

(2)第一点不完全对。为什么呢?准确来说,不是直接查找元表属性,是去元表中的一个特定的属性,名为__index的表(对象)中查找属性。__index也是一个table类型,Lua会在__index中查找相应的属性。

所以,在上面的代码中,_Square表设置了__index属性的值为自身,当为新创建的new对象查找getArea方法时,需要在原表_Square表的__index属性中查找,找到的就是getArea方法的定义。这个调用的链条如果断了,新创建的new对象的getArea方法就会导航失败。

全部评论

相关推荐

11-01 20:03
已编辑
门头沟学院 算法工程师
Lambdayo:算法岗是这样的,后端开发的牛马可就没那么幸运啦
点赞 评论 收藏
分享
11-09 01:22
已编辑
东南大学 Java
高级特工穿山甲:羡慕,我秋招有家企业在茶馆组织线下面试,约我过去“喝茶详谈”😢结果我去了发现原来是人家喝茶我看着
点赞 评论 收藏
分享
评论
点赞
收藏
分享
牛客网
牛客企业服务