OpenResty之Lua语法学习

OpenResty之Lua语法学习

前言

OpenResty,开源作者章亦春,这个开源 Web 平台主要由章亦春(agentzh)维护。在 2011年之前曾由淘宝网赞助,在后来的 2012 - 2016 年间主要由美国的 CloudFlare 公司 提供支持。目前,OpenResty® 主要由 OpenResty 软件基金会和 OpenResty Inc. 公司提供支持。

根据作者早期描述OpenResty最早是顺应OpenAPI的潮流做的,所以 Open 取自“开放”之意,而Resty便是 REST 风格的意思。虽然后来也可以基于ngx_openresty实现任何形式的 webservice 或者传统的 web 应用。OpenResty (也称为 ngx_openresty)是一个全功能的 Web 应用服务器。它打包了标准的Nginx 核心,很多的常用的第三方模块,以及它们的大多数依赖项。

通过众多 进行良好设计的 Nginx 模块,OpenResty 有效地把 Nginx 服务器转变为一个强大的 Web 应用服务器,基于它开发人员可以使用 Lua 编程语言对 Nginx 核心以及现有的各种

Nginx C 模块进行脚本编程,构建出可以处理一万以上并发请求的极端高性能的 Web 应用。OpenResty 致力于将你的服务器端应用完全运行于 Nginx 服务器中,充分利用 Nginx 的事件模型来进行非阻塞 I/O 通信。不仅仅是和 HTTP 客户端间的网络通信是非阻塞的,与MySQL、PostgreSQL、Memcached、以及 Redis 等众多远方后端之间的网络通信也是非阻塞的。因为 OpenResty 软件包的维护者也是其中打包的许多 Nginx 模块的作者,所以 OpenResty可以确保所包含的所有组件可以可靠地协同工作。

Lua

在正式使用OpenResty之前,需要先学习一下Lua语言。

Lua 基于C语言,小巧,快速,编译后仅仅一百余K,可以很方便的嵌入别的程序里,而且它的语言与C语言工作者来说极其友好,继承于C,同时也意味着它是面向过程的语言。说明:下面的代码例子全是在Lua命令行中运行的,不是SciTE,本文代码部分的 // 实际应为 --。

1.注释
// 注释和 C 语言不同,这里是用 --
--[[
 多行注释
 多行注释
 --]]

// 在默认情况下,变量总是认为是全局的。
b=10
print(b)
// 打印:10
2.关键词

and、break、do、else、elseif、end、false、for、function、if

in、local、nil、not、or、repeat、return、then、true、until、while

3.Lua数据类型

Lua是动态类型语言,变量不要类型定义,只需要为变量赋值。

  • nil,表示一个无效值(在条件表达式中相当于false)
  • boolean,跟其它语言一样,false 和 true
  • number,表示双精度类型的实浮点数
  • string,字符串类型
  • function,由 C 或 Lua 编写的函数
  • userdata,表示任意存储在变量中的C数据结构
  • thread,表示执行的独立线路,用于执行协同程序
  • table,表,也是一个"关联数组",类似数组但是有很多不同,组的索引可以是数字或者是字符串
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 值:

print(type(a))
// 运行结果:nil

boolean 类型和其他语言一样,只有两个可选值:true(真) 和 false(假),其中Lua 把 false 和 nil 看作是"假",其他的都为"真":

print(type(true))
print(type(false))
print(type(nil))

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

// 运行结果
boolean
boolean
nil
false 和 nil 都为 false!

Lua 默认只有一种 number 类型 – double(双精度)类型(默认类型可以修改 luaconf.h 里的定义),以下几种写法都被看作是 number 类型:

print(type(2))
print(type(2.2))
print(type(0.2))
print(type(2e+1))
print(type(0.2e-1))
print(type(7.8263692594256e-06))

// 运行结果
number
number
number
number
number
number

字符串由一对双引号或单引号来表示,也可以用 2 个方括号 “[[]]” 来表示"一块"字符串。

// 测试 1string1 = "this is string1"
string2 = 'this is string2'
print(string1)
// 运行结果:
this is string1

// 测试 2html = [[
<html>
<head></head>
<body>
    <div id="test">Lua测试</div>
</body>
</html>
]]
print(html)
// 运行结果
<html>
<head></head>
<body>
    <div id="test">Lua测试</div>
</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 ?

 print("a" .. 'b')
// 结果:ab
 print(157 .. 428)
// 结果:157428

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

len = "www.lua.org"
print(#len)
// 结果:11
print(#"www.lua.org")
// 结果:11

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

// 创建一个空的 table 
local tbl1 = {}

// 直接初始化表 local tbl2 = {"apple", "pear", "orange", "grape"}

// 表的索引初始化是1,不是0
local tbl = {"apple", "pear", "orange", "grape"}
for key, val in pairs(tbl) do
    print("Key", key)
end
// 运行结果: Key    1
Key    2
Key    3
Key    4

// table 不会固定长度大小,有新数据添加时 table 长度会自动增长,
// 没初始的 table 都是 nil。
a3 = {}
for i = 1, 10 do
    a3[i] = i
end
a3["key"] = "val"
print(a3["key"])
print(a3["none"])
// 运行结果: val
nil

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

function factorial1(n)
    if n == 0 then
        return 1
    else
        return n * factorial1(n - 1)
    end
end
print(factorial1(5))
factorial2 = factorial1
print(factorial2(5))
// 运行结果: 120
120

// function 可以以匿名函数(anonymous function)的方式通过参数传递:
function testFun(tab,fun)
    for k ,v in pairs(tab) do
        print(fun(k,v));
    end
end


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

在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。线程跟协程的区别:线程可以同时多个运行,而协程任意时刻只能运行一个,并且处于运行状态的协程只有被挂起(suspend)时才会暂停。userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。

4.变量

Lua 变量有三种类型:全局变量、局部变量、表中的域。Lua 中的变量全是全局变量,那怕是语句块或是函数里,除非用 local 显式声明为局部变量。局部变量的作用域为从声明位置开始到所在语句块结束。变量的默认值均为 nil,类似underfine。

a = 5               // 全局变量
local b = 5         // 局部变量

function joke()
    c = 5           // 全局变量
    local d = 6     // 局部变量
end

joke()
print(c,d)          // 5 nil

do 
    local a = 6     // 局部变量
    b = 6           // 对局部变量重新赋值
    print(a,b);     // 6 6
end

print(a,b)          // 5 6
// 运行结果:
5    nil
6    6
5    6

Lua可以对多个变量同时赋值,变量列表和值列表的各个元素用逗号分开,赋值语句右边的值会依次赋给左边的变量。

a, b = 10, 2*x  
// a=10; b=2*x

// a. 变量个数 > 值的个数      按变量个数补足nil
// b. 变量个数 < 值的个数      多余的值会被忽略
a, b, c = 0, 1
print(a,b,c)             
// 运行结果:0   1   nil

a, b = a+1, b+1, b+2     // value of b+2 is ignored
print(a,b)               
// 运行结果:1   2

a, b, c = 0
print(a,b,c)             
// 运行结果:0   nil   nil

a, b = f()
// f()返回两个值,第一个赋给a,第二个赋给b
// 应该尽可能的使用局部变量,有两个好处:避免命名冲突、访问局部变量的速度比全局变

量更快

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

t[i]
t.i                 // 当索引为字符串类型时的一种简化写法
gettable_event(t,i) // 采用索引访问本质上是一个类似这样的函数调用

site = {}
site["key"] = "www.lua.org"
print(site["key"])
// 运行结果:www.lua.org
print(site.key)
// 运行结果:www.lua.org
5.循环

循环语句这点和C语言类似,Lua 语言提供了以下几种循环处理方式:

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

循环控制就是C语言里常用的if、if…else…、if嵌套,不多赘述。

if(0)
then
    print("0 为 true")
end
// 运行结果:
0 为 true
7.函数

Lua 中我们可以将函数作为参数传递给函数,如下实例:

myprint = function(param)
   print("这是打印函数 -   ##",param,"##")
end

function add(num1,num2,functionPrint)
   result = num1 + num2
   // 调用传递的函数参数
   functionPrint(result)
end
myprint(10)
// myprint 函数作为参数传递
add(2,5,myprint)

// 运行结果:
这是打印函数 -   ##    10    ##
这是打印函数 -   ##    7    ##

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

s, e = string.find("www.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}))

// 运行结果:
23    3

Lua 函数可以接受可变数目的参数,和 C 语言类似,在函数参数列表中使用三点 … 表示函数有可变的参数。如果我们需要几个固定参数加上可变参数,固定参数必须放在变长参数之前:

function fwrite(fmt, ...)  ---> 固定的参数fmt
    return io.write(string.format(fmt, ...))     
end

通常在遍历变长参数的时候只需要使用 {…},然而变长参数可能会包含一些 nil,那么就可以用 select 函数来访问变长参数了:select(‘#’, …) 或者 select(n, …) select(‘#’, …) 返回可变参数的长度select(n, …) 用于访问 n 到 select(‘#’,…) 的参数调用select时,必须传入一个固定实参selector(选择开关)和一系列变长参数。如果selector为数字n,那么select返回它的第n个可变实参,否则只能为字符串"#",这样select会返回变长参数的总数。

do  
    function foo(...)  
        for i = 1, select('#', ...) do  -->获取参数总数
            local arg = select(i, ...); -->读取参数
            print("arg", arg);  
        end  
    end  

    foo(1, 2, 3, 4);  
end

// 运行结果:
arg    1
arg    2
arg    3
arg    4
8.运算符

Lua的运算符除了常用的是算术运算符、关系运算符、逻辑运算符等等;区别在于与、或、非,这里用and、or、not 代替。其他运算符:… 表示字符串连接符、#一元运算符,返回字符串或表的长度。

9.字符串

Lua相对于C语言,除了单、双引号,多了一种字符串的表示:[[]]。字符串方法有:

// 1.字符串全部转为大写字母。
string.upper(argument)

// 2.字符串全部转为小写字母。
string.lower(argument)

// 3.在字符串中替换,mainString为要替换的字符串,findString 为被替换的字符,
// replaceString 要替换的字符,num 替换次数(可以忽略,则全部替换)
string.gsub(mainString,findString,replaceString,num)

// 4.搜索指定的内容(第三个参数为索引),返回其具体位置,不存在则返回 nil。
string.find (str, substr, [init, [end]])

// 5.字符串反转
string.reverse(arg)

// 6.返回一个类似printf的格式化字符串
string.format(...)

// 7.char 将整型数字转成字符并连接, byte 转换字符为整数值
string.char(arg) 和 string.byte(arg[,int])

// 8.计算字符串长度。
string.len(arg)

// 9.返回字符串string的n个拷贝
string.rep(string, n)

// 10.链接两个字符串
..

// 11.回一个迭代器函数,每一次调用这个函数,
// 返回一个在字符串 str 找到的下一个符合 pattern 描述的子串
string.gmatch(str, pattern)

// 12.string.match()只寻找源字串str中的第一个配对. 参数init可选,
string.match(str, pattern, init)

对字符串进行查找与反转操作:

string = "Lua Tutorial"
// 查找字符串
print(string.find(string,"Tutorial"))
reversedString = string.reverse(string)
print("新字符串为",reversedString)

// 运行结果:
5    12
新字符串为    lairotuT auL

字符与整数相互转换:

// 转换第一个字符
print(string.byte("Lua"))
// 转换第三个字符
print(string.byte("Lua",3))
// 转换末尾第一个字符
print(string.byte("Lua",-1))
// 第二个字符
print(string.byte("Lua",2))
// 转换末尾第二个字符
print(string.byte("Lua",-2))
// 整数 ASCII 码转换为字符
print(string.char(97))

// 运行结果:
76
97
97
117
117
a
10.数组

数组,就是相同数据类型的元素按一定顺序排列的集合,可以是一维数组和多维数组。Lua数组的索引键值可以使用整数表示,数组的大小不是固定的。

// 一维数组,线性表,
array = {"Lua", "Tutorial"}
for i= 0, 2 do
   print(array[i])
end
// 运行结果:
nil
Lua
Tutorial


// 多维数组,数组中包含数组或一维数组的索引键对应一个数组
// 初始化数组
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
11.迭代器

迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。在Lua中迭代器是一种支持指针类型的结构,它可以遍历集合的每一个元素。泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量,泛型 for 迭代器提供了集合的 key/value 对。

array = {"Lua", "Tutorial"}
for key,value in ipairs(array) 
do
   print(key, value)
end
// 运行结果:
1  Lua
2  Tutorial

Lua 泛型 for 的执行过程:

  • 首先,初始化,计算in后面表达式的值,表达式应该返回泛型 for 需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用nil补足,多出部分会被忽略。
  • 第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于for结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
  • 第三,将迭代函数返回的值赋给变量列表。
  • 第四,如果返回的第一个值为nil循环结束,否则执行循环体。
  • 第五,回到第二步再次调用迭代函数

在Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。Lua 的迭代器包含以下两种类型:无状态的迭代器、多状态的迭代器。无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。每一次迭代,迭代函数都是用两个变量(状态常量和控制变量)的值作为参数被调用,一个无状态的迭代器只利用这两个值可以获取下一个元素。这种无状态迭代器的典型的简单的例子是ipairs,它遍历数组的每一个元素。

function square(iteratorMaxCount,currentNumber)
   if currentNumber<iteratorMaxCount
   then
      currentNumber = currentNumber+1
   return currentNumber, currentNumber*currentNumber
   end
end

for i,n in square,3,0
do
   print(i,n)
end
// 运行结果:
1    1
2    4
3    9

很多情况下,迭代器需要保存多个状态信息而不是简单的状态常量和控制变量,最简单的方法是使用闭包,还有一种方法就是将所有的状态信息封装到table内,将table作为迭代器的状态常量,因为这种情况下可以将所有的信息存放在table内,所以迭代函数通常不需要第二个参数。

array = {"Lua", "Tutorial"}

function elementIterator (collection)
   local index = 0
   local count = #collection
   -- 闭包函数
   return function ()
      index = index + 1
      if index <= count
      then
         --  返回迭代器的当前元素
         return collection[index]
      end
   end
end

for element in elementIterator(array)
do
   print(element)
end
// 运行结果:
Lua
Tutorial
12.Lua table(表)

table 是 Lua 的一种数据结构用来帮助我们创建不同的数据类型,如:数组、字典等。Lua table 使用关联型数组,你可以用任意类型的值来作数组的索引,但这个值不能是 nil。Lua table 是不固定大小的,你可以根据自己需要进行扩容。Lua也是通过table来解决模块(module)、包(package)和对象(Object)的。 例如string.format表示使用"format"来索引table string。

当我们为 table a 并设置元素,然后将 a 赋值给 b,则 a 与 b 都指向同一个内存。如果 a 设置为 nil ,则 b 同样能访问 table 的元素。如果没有指定的变量指向a,Lua的垃圾回收机制会清理相对应的内存。

// 简单的 table
mytable = {}
print("mytable 的类型是 ",type(mytable))

mytable[1]= "Lua"
mytable["wow"] = "修改前"
print("mytable 索引为 1 的元素是 ", mytable[1])
print("mytable 索引为 wow 的元素是 ", mytable["wow"])

// alternatetable和mytable的是指同一个 table
alternatetable = mytable

print("alternatetable 索引为 1 的元素是 ", alternatetable[1])
print("mytable 索引为 wow 的元素是 ", alternatetable["wow"])

alternatetable["wow"] = "修改后"

print("mytable 索引为 wow 的元素是 ", mytable["wow"])

// 释放变量
alternatetable = nil
print("alternatetable 是 ", alternatetable)

// mytable 仍然可以访问
print("mytable 索引为 wow 的元素是 ", mytable["wow"])

mytable = nil
print("mytable 是 ", mytable)

// 运行结果:
mytable 的类型是     table
mytable 索引为 1 的元素是     Lua
mytable 索引为 wow 的元素是     修改前
alternatetable 索引为 1 的元素是     Lua
mytable 索引为 wow 的元素是     修改前
mytable 索引为 wow 的元素是     修改后
alternatetable 是     nil
mytable 索引为 wow 的元素是     修改后
mytable 是     nil

Table 操作常用的方法:

  • table.concat (table [, sep [, start [, end]]])

concat是concatenate(连锁, 连接)的缩写. table.concat()函数列出参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep)隔开。

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

在table的数组部分指定位置(pos)插入值为value的一个元素. pos参数可选, 默认为数组部分末尾.

  • table.maxn (table)

指定table中所有正数key值中最大的key值. 如果不存在key值为正数的元素, 则返回0。(Lua5.2之后该方法已经不存在了,本文使用了自定义函数实现)。

  • table.remove (table [, pos])

返回table数组部分位于pos位置的元素. 其后的元素会被前移. pos参数可选, 默认为table长度, 即从最后一个元素删起。

  • table.sort (table [, comp])

对给定的table进行升序排序。

13.Lua 模块与包

这也是 Lua 强力的地方,模块类似于一个封装库,从 Lua 5.1 开始,Lua 加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以 API 接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。

Lua 的模块是由变量、函数等已知元素组成的 table,因此创建一个模块很简单,就是创建一个 table,然后把需要导出的常量、函数放入其中,最后返回这个 table 就行。

// 文件名为 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 里的元素那样来操作调用模块里的常量或函数。Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。

// require 函数
require("<模块名>")
// 或者
require "<模块名>"

// 执行 require 后会返回一个由模块常量或函数组成的 table
// 并且还会定义一个包含该 table 的全局变量
// test_module.lua 文件
// module 模块为上文提到到 module.lua
require("module")
print(module.constant)
module.func3()
// 运行结果:
这是一个常量
这是一个私有函数!

// 或者给加载的模块定义一个别名变量,方便调用,比如
local m = require("module")

加载机制

对于自定义的模块,模块文件不是放在哪个文件目录都行,函数 require 有它自己的文件路径加载策略,它会尝试从 Lua 文件或 C 程序库中加载模块。

require 用于搜索 Lua 文件的路径是存放在全局变量 package.path 中,当 Lua 启动后,会以环境变量 LUA_PATH 的值来初始这个环境变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。


C 包

Lua和C是很容易结合的,使用C为Lua写包。与Lua中写包不同,C包在使用以前必须首先加载并连接,在大多数系统中最容易的实现方式是通过动态连接库机制。Lua在一个叫loadlib的函数内提供了所有的动态连接的功能。loadlib函数加载指定的库并且连接到Lua,然而它并不打开库(也就是说没有调用初始化函数),反之他返回初始化函数作为Lua的一个函数,这样我们就可以直接在Lua中调用他。如果加载动态库或者查找初始化函数时出错,loadlib将返回nil和错误信息。

一般情况下我们期望二进制的发布库包含一个与前面代码段相似的stub文件,安装二进制库的时候可以随便放在某个目录,只需要修改stub文件对应二进制库的实际路径即可。


14.元表(Metatable)

在 Lua table 中我们可以访问对应的key来得到value值,但是却无法对两个 table 进行操作。因此 Lua 提供了元表(Metatable),允许我们改变table的行为,每个行为关联了对应的元方法。有两个很重要的函数来处理元表:

  • setmetatable(table,metatable):

对指定 table 设置元表(metatable),如果元表(metatable)中存在 __metatable 键值,setmetatable 会失败。

  • getmetatable(table)

返回对象的元表(metatable)。

mytable = {}                          // 普通表 
mymetatable = {}                      // 元表
setmetatable(mytable,mymetatable)     // 把 mymetatable 设为 mytable 的元表 
// 也可以直接写成一行
mytable = setmetatable({},{})
// 返回对象元表
getmetatable(mytable)

1.__index 元方法

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

如果__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

2.__newindex 元方法

__newindex 元方法用来对表更新,__index则用来对表访问 。当你给表的一个缺少的索引赋值,解释器就会查找__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

3.__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

4.__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
15.协同程序(coroutine)

Lua 协同程序(coroutine)与线程比较类似:拥有独立的堆栈,独立的局部变量,独立的指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。线程与协同程序的主要区别在于,一个具有多个线程的程序可以同时运行几个线程,而协同程序却需要彼此协作的运行。在任一指定时刻只有一个协同程序在运行,并且这个正在运行的协同程序只有在明确的被要求挂起的时候才会被挂起,协同程序有点类似同步的多线程,在等待同一个线程锁的几个线程有点类似协同。

  • coroutine.create()
    创建coroutine,返回coroutine, 参数是一个函数,当和resume配合使用的时候就唤醒函数调用。
  • coroutine.resume()
    重启coroutine,和create配合使用。
  • coroutine.yield()
    挂起coroutine,将coroutine设置为挂起状态,这个和resume配合使用能有很多有用的效果。
  • coroutine.status()
    查看coroutine的状态,coroutine的状态有三种:dead,suspend,running,具体什么时候有这样的状态请参考下面的程序。
  • coroutine.wrap()
    创建coroutine,返回一个函数,一旦你调用这个函数,就进入coroutine,和create功能重复。
  • coroutine.running()
    返回正在跑的coroutine,一个coroutine就是一个线程,当使用running的时候,就是返回一个corouting的线程号。
function foo (a)
    print("foo 函数输出", a)
    return coroutine.yield(2 * a) 
    // 这里返回  2*a 的值
end

co = coroutine.create(function (a , b)
    print("第一次协同程序执行输出", a, b) 
    // co-body 1 10
    local r = foo(a + 1)

    print("第二次协同程序执行输出", r)
    local r, s = coroutine.yield(a + b, a - b)  
    // a,b的值为第一次调用协同程序时

传入

    print("第三次协同程序执行输出", r, s)
    return b, "结束协同程序"                   
    // b的值为第二次调用协同程序时传入
end)

print("main", coroutine.resume(co, 1, 10)) // true, 4
print("--分割线----")
print("main", coroutine.resume(co, "r")) // true 11 -9
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) // true 10 end
print("---分割线---")
print("main", coroutine.resume(co, "x", "y")) // cannot resume dead coroutine
print("---分割线---")

// 运行结果:
第一次协同程序执行输出    1    10
foo 函数输出    2
main    true    4
--分割线----
第二次协同程序执行输出    r
main    true    11    -9
---分割线---
第三次协同程序执行输出    x    y
main    true    10    结束协同程序
---分割线---
main    false    cannot resume dead coroutine
---分割线---

上面这个demo的运行过程如下:

  • 调用resume,将协同程序唤醒,resume操作成功返回true,否则返回false;
  • 协同程序运行;
  • 运行到yield语句;
  • yield挂起协同程序,第一次resume返回;(注意:此处yield返回,参数是resume的参数)
  • 第二次resume,再次唤醒协同程序;(注意:此处resume的参数中,除了第一个参数,剩下的参数将作为yield的参数)
  • yield返回;
  • 协同程序继续运行;
  • 如果使用的协同程序继续运行完成后继续调用 resume方法则输出:cannot resume dead coroutine。resume和yield的配合强大之处在于,resume处于主程中,它将外部状态(数据)传入到协同程序内部;而yield则将内部的状态(数据)返回到主程中。
16.文件 I/O

和C语言一样,Lua I/O 库用于读取和处理文件。分为简单模式、完全模式。

  • 简单模式(simple model)
    拥有一个当前输入文件和一个当前输出文件,并且提供针对这些文件相关的操作。
// 以只读方式打开文件
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)

//  io.read() 中参数
//  读取一个数字并返回它: file.read("*n")
//  从当前位置读取整个文件: file.read("*a")
//  读取下一行,在文件尾 (EOF) 处返回 nil :file.read("*l")
//  返回一个指定字符个数的字符串,或在EOF时返回nil:file.read(5)

// 返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除
io.tmpfile()

// 检测obj是否一个可用的文件句柄
io.type(file)

// 向文件写入缓冲中的所有数据
io.flush()

// 返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,但不关闭文件
io.lines(optional file name)

  • 完全模式(complete model)
    使用外部的文件句柄来实现。它以一种面对对象的形式,将所有的文件操作定义为文件句柄的方法。

简单模式在做一些简单的文件操作时较为合适。但是在进行一些高级的文件操作的时候,简单模式就显得力不从心。例如同时读取多个文件这样的操作,使用完全模式则较为合适。

// 以只读方式打开文件
file = io.open("test.lua", "r")

// 输出文件第一行
print(file:read())

// 关闭打开的文件
file:close()

// 以附加的方式打开只写文件
file = io.open("test.lua", "a")

// 在文件最后一行添加 Lua 注释
file:write("--test")

// 关闭打开的文件
file:close()

// 设置和获取当前文件位置,成功则返回最终的文件位置(按字节)
file:seek(optional whence, optional offset)

// 向文件写入缓冲中的所有数据
file:flush()

// 打开指定的文件filename为读模式并返回一个迭代函数,
// 每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,并自动关闭文件。
io.lines(optional file name)

// read 的参数与简单模式一致,不过参数 whence 值可以是:
// "set": 从文件头开始
// "cur": 从当前位置开始[默认]
// "end": 从文件尾开始
// offset:默认为0
17.错误处理

程序运行中错误处理是必要的,在我们进行文件操作,数据转移及web service 调用过程中都会出现不可预期的错误。如果不注重错误信息的处理,就会造成信息泄露,程序无法运行等情况。

我们可以使用两个函数:asserterror 来处理错误。assert首先检查第一个参数,若没问题,assert不做任何事情;否则,assert以第二个参数作为错误信息抛出;而error函数,会终止正在执行的函数,并返回message的内容作为错误信息(error函数永远都不会返回),通常情况下,error会附加一些错误位置的信息到message头部。

pcall 和 xpcall、debug,是处理异常的常用方法,使用函数pcall(protected call)来包装需要执行的代码。pcall接收一个函数和要传递给后者的参数,并执行,执行结果:有错误、无错误;返回值true或者或false, errorinfo。pcall以一种"保护模式"来调用第一个参数,因此pcall可以捕获函数执行中的任何错误。

if pcall(function_name, ….) then
// 没有错误
else
// 一些错误
end

Lua提供了xpcall函数,xpcall接收第二个参数——一个错误处理函数,当错误发生时,Lua会在调用桟展开(unwind)前调用错误处理函数,于是就可以在这个函数中使用debug库来获取关于错误的额外信息了。

debug库提供了两个通用的错误处理函数:debug.debug,通过提供一个Lua提示符,让用户来检查错误的原因;debug.traceback,根据调用桟来构建一个扩展的错误消息。

// 例如
function myfunction ()
   n = n/nil
end

function myerrorhandler( err )
   print( "ERROR:", err )
end

status = xpcall( myfunction, myerrorhandler )
print( status)

// 运行结果:
ERROR:    test2.lua:2: attempt to perform arithmetic on global 'n' (a nil value)
false
18.调试(Debug)

Lua 提供了 debug 库用于提供创建我们自定义调试器的功能。Lua 本身并未有内置的调试器,但很多开发者共享了他们的 Lua 调试器代码。Lua 中 debug 库包含以下函数:

debug()
// 进入一个用户交互模式,运行用户输入的每个字符串。 

getfenv(object):
// 返回对象的环境变量。

gethook(optional thread):
// 返回三个表示线程钩子设置的值: 当前钩子函数,当前钩子掩码,当前钩子计数

getinfo ([thread,] f [, what]):
// 返回关于一个函数信息的表。 

debug.getlocal ([thread,] f, local):
// 此函数返回在栈的 f 层处函数的索引为 local 的局部变量 的名字和值。

getmetatable(value):
// 把给定索引指向的值的元表压入堆栈。

getregistry():
// 返回注册表表。

getupvalue (f, up)
// 此函数返回函数 f 的第 up 个上值的名字和值。

sethook ([thread,] hook, mask [, count]):
// 将一个函数作为钩子函数设入。

setlocal ([thread,] level, local, value):
// 这个函数将 value 赋给 栈上第 level 层函数的第 local 个局部变量。

setmetatable (value, table):
// 将 value 的元表设为 table (可以是 nil)。

setupvalue (f, up, value):
// 这个函数将 value 设为函数 f 的第 up 个上值。 

traceback ([thread,] [message [, level]]):
// 如果 message 有,且不是字符串或 nil, 函数不做任何处理直接返回 message。
19.垃圾回收

Lua 采用了自动内存管理。 这意味着你不用操心新创建的对象需要的内存如何分配出来, 也不用考虑在对象不再被使用后怎样释放它们所占用的内存。

Lua 提供了以下函数collectgarbage ([opt [, arg]])用来控制自动内存管理:

  • collectgarbage(“collect”):
    做一次完整的垃圾收集循环。通过参数 opt 它提供了一组不同的功能:
  • collectgarbage(“count”):
    以 K 字节数为单位返回 Lua 使用的总内存数。 这个值有小数部分,所以只需要乘上 1024 就能得到 Lua 使用的准确字节数(除非溢出)。
  • collectgarbage(“restart”):
    重启垃圾收集器的自动运行。
  • collectgarbage(“setpause”):
    将 arg 设为收集器的 间歇率 (参见 §2.5)。 返回 间歇率 的前一个值。
  • collectgarbage(“setstepmul”):
    返回 步进倍率 的前一个值。
  • collectgarbage(“step”):
    单步运行垃圾收集器。 步长"大小"由 arg 控制。 传入 0 时,收集器步进(不可分割的)一步。 传入非 0 值, 收集器收集相当于 Lua 分配这些多(K 字节)内存的工作。 如果收集器结束一个循环将返回 true 。
  • collectgarbage(“stop”):
    停止垃圾收集器的运行。 在调用重启前,收集器只会因显式的调用运行。

举个代码片段:

mytable = {"apple", "orange", "banana"}

print(collectgarbage("count"))

mytable = nil

print(collectgarbage("count"))

print(collectgarbage("collect"))

print(collectgarbage("count"))

// 运行结果:
20.9560546875
20.9853515625
0
19.4111328125
20.面向对象

Lua 是基于C语言的面向过程语言,lua中的function可以用来表示方法。那么LUA中的类可以通过table + function模拟出来,这里并没有实际的class方法。

猜你喜欢

转载自blog.csdn.net/robinhunan/article/details/129784119