一文览遍Lua

前言

毕业近三年,目前在一家ToC产品公司负责服务端开发,以前没有接触使用过Lua,仅仅是知道这个东西,但是在一些较为复杂的业务逻辑中,需要使用到Lua才可以更好的实现,所以现在对于Lua进行一个简单的学习记录。

初识Lua

Lua是什么?

Lua 是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。它于1993年被巴西里约热内卢天主教大学的几个大学生开发出来。

Lua特性

  • 轻量性:它是使用标准C语言编写并以源代码形式开放,编译之后就100+K。
  • 可扩展:Lua提供了非常易于使用的扩展接口和机制。
  • 支持面向过程和函数式编程
  • 自动内存管理
  • 语言内置模式匹配;闭包;提供多线程支持
  • 通过闭包和table可以很方便地支持面向对象编程所需要的一些关键机制

Lua应用场景

目前自己接触到的较为常见的用法有Nginx+Lua,redis+Lua。Lua主要的应用场景有以下几种:

  • 游戏开发
  • 独立应用脚本
  • Web应用脚步
  • 扩展和数据库插件
  • 安全系统

Lua的安装

目前最新版本是5.4了,不过这里还是以5.3来操作。Lua网址,最新版本源码安装指导。

Linux系统下安装

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

Linux系统下的包安装:

yum install lua
apt install lua

Mac系统下安装

Mac系统下的源码安装:

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 macosx test
make install

Mac系统下的包安装:

brew install lua

Lua基本语法

交互式编程

Lua也和python等其他编程语言一样,提供了交互式编程。使用lua或者lua -i进入交互式编程界面:

liuxionghui@bogon lua-5.3.0 % lua -i
Lua 5.3.0  Copyright (C) 1994-2015 Lua.org, PUC-Rio
> print("Hello World!")
Hello World!
> 

脚本式编程

脚本式编程就更好理解了,就是将Lua代码以lua文件保存,并执行。
比如我将print("Hello World!")写入文件中:

liuxionghui@bogon lua_code % cat test.lua 
#!/usr/local/bin/lua
print("Hello World")
liuxionghui@bogon lua_code % lua test.lua 
Hello World
liuxionghui@bogon lua_code % ./test.lua 
Hello World

其中第一行,熟悉linux的应该不陌生,它为这个脚步指定了解释器。

注释

不管在什么语言中,注释都是不可缺失的东西,Lua也不例外。

#!/usr/local/bin/lua
-- 这是一行注释
--[[
这是
多行注释
--]]
print("Hello World")

标示符

Lua 标示符用于定义一个变量,函数获取其他用户定义的项。它以字母或者_下划线开头,后面加0或者多个字母、下划线或者数字。

Lua的标示符需要注意以下几个方面:

  • 不要使用下划线加大写字母的标示符,因为Lua保留字是这么设置的
  • Lua不能使用特殊符号来定义标示符
  • Lua标示符区分大小写,a和A是两个不同的标示符

关键词

以下列出了 Lua 的保留关键词。保留关键字不能作为常量或变量或其他用户自定义标示符:

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

全局变量

在Lua中,变量默认就是全局的。而且访问一个没有初始化的全局变量也不会出错,只不过得到的结果是:nil。如果你想删除一个全局变量,只需要将变量赋值为nil。

Lua数据类型

Lua和python一样,是一种动态类型语言,变量不需要定义,只需要为变量赋值即可。

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 的创建是通过"构造表达式"来完成,最简单构造表达式是{},用来创建一个空表。
liuxionghui@bogon ~ % lua
Lua 5.3.0  Copyright (C) 1994-2015 Lua.org, PUC-Rio
> print(type(123))
number
> return print(type(nil))
nil
> return print(type(false))
boolean
> return print(type('hello world'))
string
> return print(type(type))
function

nil

nil 类型表示一种没有任何有效值,它只有一个值 -- nil,例如打印一个没有赋值的变量,便会输出一个 nil 值。对于全局变量和 table,nil 还有一个"删除"作用,给全局变量或者 table 表里的变量赋一个 nil 值,等同于把它们删掉。

注意:type返回的nil为字符串!!!

> return print(type(a))
nil
> tab1 = { key1 = "val1", key2 = "val2", "val3" }
> for k, v in pairs(tab1) do
>>     print(k .. " - " .. v)
>> end
1 - val3
key2 - val2
key1 - val1
>  
> tab1.key1 = nil
> for k, v in pairs(tab1) do
>>     print(k .. " - " .. v)
>> end
1 - val3
key2 - val2
> type(x) == nil
false
> return type(x) == 'nil'
true

boolean

boolean类型只有两个值,true和false。不过在lua中的boolean类型转换与其他一些语言不同。在Lua中,只有false和nil是false,其余都是true,包括0。

注意:在python、C、node等语言中,0和空字符串是false而不是true。(其余语言本人不了解)

> false
false
> true
true
> not not nil
false
> not not 0
true
> return not not ''
true

number

Lua中默认只有一种number类型(double),可以通过修改luaconf.h中的定义来更改。

> return print(type(1))
number
> return print(type(1.23))
number
> return print(type(-0.23))
number
> return print(type(2e+3))
number
> return print(type(2e+3-1))
number

string

在Lua中字符串由一对双引号或者单引号,两个方括号表示一块字符串。

注意:对于字符串进行算术运算时,lua会自动将字符串转换为number,无法转换的话会抛出异常,在lua中,字符串的拼接使用".."。

> print('2' + 5)
7.0
> return print('2' + '123')
125.0
> return print('xxx' + '123')
stdin:1: attempt to perform arithmetic on a string value
stack traceback:
	stdin:1: in main chunk
	[C]: in ?
> return print("123" + '123')
246.0
> return print([[hello world]])
hello world
> return print('123' .. '456')
123456

table

在lua中,table的创建通过构造表达式来完成,最简单的表是空表-{}。

注意:可能看到{}就会想到其他语言中的对象或者dict字典,但是在lua中table和他们有一些不同。

  • Lua 中的表(table)其实是一个"关联数组"(associative arrays),数组的索引可以是数字或者是字符串。
  • lua中的数字索引默认是从1开始的!!!
  • lua中table不会固定大小,获取不存在的数据只会返回nil,不会抛出异常
-- 创建一个空的 table
local tbl1 = {}
-- 直接初始表
local tbl2 = {"apple", "pear", "orange", "grape"}

> tbl = {"apple","pear","orange"}                                               > for key, val in pairs(tbl) do                                                     print("Key", key, val)                                                      end
Key	1	apple
Key	2	pear
Key	3	orange

function

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

其它

  • thread:线程,在 Lua 里,最主要的线程是协同程序(coroutine)。它跟线程(thread)差不多,拥有自己独立的栈、局部变量和指令指针,可以跟其他协同程序共享全局变量和其他大部分东西。
  • userdata:自定义类型,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct 和 指针)存储到 Lua 变量中调用。

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

Lua变量

不管在什么语言中,要使用变量,就需要先声明变量(创建变量)。

变量类型

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

在Lua中,只要是没有特别声明是局部变量的变量都是全局变量,这也是Lua和其他一些语言不同的地方,需要稍微注意一下。

局部变量,就是使用local显式声明了的变量,该变量的作用域是声明位置所在的代码块。

#!/usr/local/bin/lua
function sum(x, y)
  local z = x + y
  k = x + y
  return z
end
print(sum(1,2))
print(z)
print(k)

运行结果:

3
nil
3

在上面的代码中,z是一个局部变量,函数sum结束之后,z就不存在了。而k是一个全局变量,函数sum结束之后k依然存在。

注意:你需要尽可能多的使用局部变量,因为全局变量太多不容易管理,而且内存空间也得不到及时释放,同时访问局部变量比全局变量快。

赋值语句

与其他语言一样,lua中使用“=”来对变量进行赋值。不过lua可以同时对多个变量同时赋值:

> a = 3
> b, c = 4, 5
> print(a,b,c)
3	4	5

看到这里,是不是觉得和python的赋值一样,但是它们是不一样,接着看一下下面的语句:

> i,j,k = 1,2
> print(i,j,k)
1	2	nil
> i,j = 1,2,3
> print(i,j)
1	2

Lua中前后数量不一时,也不会抛出异常,它只会按前后赋值,没有值了或者没有变量了就不管了,所以k是没有进行赋值的。

变量个数 > 值的个数             按变量个数补足nil
变量个数 < 值的个数             多余的值会被忽略

索引

Lua中对table的索引可以使用[]和“.”。

> t = {}
> t.i = 123
> t.i
123
> t['i']
123

Lua循环

lua中提供了多种循环处理方式:

  • while循环
  • for循环
  • repeat unit循环

for循环

Lua中for循环有两大用法。

数值for循环

Lua中的数值for循环语法格式如下(var从exp1变化到exp2,每次变化以exp3为步长递增var,并执行一次”执行体”。exp3是可选的,如果不指定,默认为1):

for var=exp1,exp2,exp3 do     
 <执行体> 
 end  

案例:

> for i=0,5 do
>> print(i)
>> end
0
1
2
3
4
5
> for i=0,5,2 do
>> print(i)
>> end
0
2
4

泛型for循环

泛型for循环通过一个迭代器函数来遍历所有值,类似java中的foreach语句。

格式:

-- i是数组索引值,v是对应索引的数组元素值。
-- ipairs是Lua提供的一个迭代器函数,用来迭代数组。
for i,v in ipairs(a) 
    do print(v) 
end 

案例:

> t = {"a","b","c",d = "d"}
> for i,v in ipairs(t)
>> do print(i,v)
>> end
1	a
2	b
3	c
> t.d
d

注意:像上面一样的,字符串为key的无法遍历出来。

while循环

while循环就显得简单了很多,直接看格式:

while(condition)

 do

 statements

end

statements(循环体语句) 可以是一条或多条语句,condition(条件) 可以是任意表达式,在 condition(条件) 为 true 时执行循环体语句。

repeat...until

Lua中的repeat...until循环其实很像大部分编程语言中的do...while循环,不过是循环的判断不大一样=.=。直接看一下格式:

repeat
   statement(s)
until( condition )

在循环语句(S)执行一次之前的条件进行测试。如果条件为false,控制流程跳转备份执行循环语句(S)再次执行。这个过程反复进行,直到给定的条件变为真。

循环控制

循环控制语句用于控制程序的流程, 以实现程序的各种结构方式。
主要是下面两种:

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

Lua条件判断

在lua中的条件判断使用的是if和else,这个比较简单,直接记录一下案例。

> -- if案例
> a = 3
> if (a < 5) then
>> print('a<5')
>> end
a<5
>-- if else 案例
> a = 3
> if (a > 5) then
>> print('a>5')
>> else
>> print('a<=5')
>> end
a<=5
> --if elseif 案例
> a = 3
> if (a>3) then
>> print('a>3')
>> elseif (a<3) then
>> else 
>> print('a=3')
>> end
a=3

Lua函数

Lua中,函数是对语句和表达式进行抽象的主要方法。Lua虽然小,但是也还是提供了大量的内建函数,可以很方便的调用他们,比如type、print都是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:函数名称
  • argumentx:函数参数,个数不定,0-N
  • function_body:函数体,函数执行的代码块
  • result_params_comma_separated:函数返回值,可以返回多个,也可以不返回

案例:

> function sum(a,b)
>> return a+b
>> end
> print(sum(1,2))
3

多返回值

这个没什么介绍的,就是一个函数可以返回多个值:

> function he(a,b)
>> return a+b,a-b
>> end
> print(he(4,2))
6	2

可变参数

Lua中函数使用...来接受可变参数,也可以固定参数和可变参数一起使用,不过固定参数一定要在可变参数前。
案例:

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

运行结果:

总共传入 6 个数
平均值为    5.5

这个可变参数还有两个东西需要了解一下:

select('#', …) 返回可变参数的长度
select(n, …) 用于返回 n 到 select('#',…) 的参数

Lua运算符

运算符是一个特殊得符号,用来执行特定的数字或者逻辑运算。

算术运算符

算术运算符 备注
+ 加法
- 减法
* 乘法
/ 除法
% 取余
^ 乘幂

案例:

> print(2+1)
3
> return print(2-1)
1
> return print(2*3)
6
> return print(6/3)
2.0
> return print(6%4)
2
> return print(6^4)
1296.0

关系运算符

关系运算符 备注
== 等于
~= 不等于
> 大于
< 小于
>= 大于等于
<= 小于等于

案例:

> return print(2 == 2)
true
> return print(2 ~= 2)
false
> return print(3 > 2)
true
> return print(3 < 2)
false
> return print(3 >= 2)
true
> return print(3 <= 2)
false

逻辑运算符

逻辑运算符 备注
and 逻辑与操作符
or 逻辑或操作符
not 逻辑非操作符

案例:

> return print(1 and 3)
3
> return print(1 or 0)
1
> return print(not 0)
false

其他运算符

其他运算符 备注
.. 连接两个字符串
# 返回字符串或表的长度

案例:

> return print('123' .. '456')
123456
> return print(#'456')
3
> return print(#{'1','2','3','4'})
4

运算符优先级

这个东西个人觉得,没什么记的必要,编码时善于使用“()”,这样逻辑看上去也清楚很多。这里只是简单记录一下优先级,优先级从上到下:

^
not    - (unary)
*      /       %
+      -
..
<      >      <=     >=     ~=     ==
and
or

Lua字符串

字符串是由数字、字母、下划线和符号组成的一串字符,Lua中的转义字符和其余语言无异,直接跳过,主要了解一些字符串操作。

字符串操作

Lua中提供了大量的内置函数操作字符串:

方法 备注
string.upper(argument) 字符串全部转为大写字母
string.lower(argument) 字符串全部转为小写字母
string.gsub(mainString,findString,replaceString,num) 在字符串中替换。mainString 为要操作的字符串, findString 为被替换的字符,replaceString 要替换的字符,num 替换次数(可以忽略,则全部替换
string.find (str, substr, [init, [end]]) 在一个指定的目标字符串中搜索指定的内容(第三个参数为索引),返回其具体位置。不存在则返回 nil
string.reverse(arg) 字符串反转
string.format(...) 返回一个类似printf的格式化字符串
string.char(arg) 将整型数字转成字符并连接
string.byte(arg[,int]) 转换字符为整数值(可以指定某个字符,默认第一个字符)
string.len(arg) 计算字符串长度
string.rep(string, n) 返回字符串string的n个拷贝
.. 链接两个字符串
string.gmatch(str, pattern) 回一个迭代器函数,每一次调用这个函数,返回一个在字符串 str 找到的下一个符合 pattern 描述的子串。如果参数 pattern 描述的字符串没有找到,迭代函数返回nil
string.match(str, pattern, init) string.match()只寻找源字串str中的第一个配对. 参数init可选, 指定搜寻过程的起点, 默认为1
string.sub(s, i [, j]) 截取字符串,s为字符串,i是开始位置,j是结束位置,默认-1最后一个字符

案例:

> return print(string.upper('12strSTr'))
12STRSTR
> return print(string.lower('12strSTr'))
12strstr
> return print(string.gsub('12strSTr','r','x',1))
12stxSTr	1
> return print(string.find('123hello world', 'o wr',1,20))
nil
> return print(string.find('123hello world', 'o wo',1,20))
8	11
> return print(string.reverse('hello world'))
dlrow olleh
> return print(string.format('hello %s','world'))
hello world
> return print(string.char(97,98,99,100,101))
abcde
> return print(string.byte('abcd',1))
97
> return print(string.byte('abcd'))
97
> return print(string.len('123456qwer'))
10
> return print(string.rep('lo',4))
lolololo
> return print('123' .. '456')
123456
> for word in string.gmatch("Hello Lua user", "%a+")
>> do
>> print(word)
>> end
Hello
Lua
user
> string.match("I have 5 questions for you.", "%d+ %a+")
5 questions
> return print(string.sub('1234567890',5))
567890
> return print(string.sub('1234567890',5,-3))
5678
> return print(string.sub('1234567890',5,15))
567890
> return print(string.sub('1234567890',-4,15))

Lua数组

数组:数据元素按一定顺序排列的集合,可以是一维或者多维。Lua数组的索引键值使用整数表示,Lua数组大小也不是固定。

一维数组

一维数组是最简单的数组,也是数组入门,其逻辑结构是线性表。

注意:Lua数组的下标从1开始。

案例:

> arr = {'a','b','c'}
> for i=0,3 do
>> print(arr[i])
>> end
nil
a
b
c

多维数组

多维数组即数组中包含数组或一维数组的索引键对应一个数组,这里记录一个二维数组案例:

> 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 迭代器

迭代器是一种用来遍历标准模板库容器中元素的对象,每一个迭代器对象表示了容器中确定的地址。

Lua迭代器是一种支持指针类型的结构,可以遍历每一个元素。

Lua中我们常常使用函数来描述迭代器,每次调用该函数就返回集合的下一个元素。Lua 的迭代器包含以下两种类型:

  • 无状态的迭代器
  • 多状态的迭代器

泛型for迭代器

泛型 for 在自己内部保存迭代函数,实际上它保存三个值:迭代函数、状态常量、控制变量。

泛型 for 迭代器提供了集合的 key/value 对,语法格式如下:

for k, v in pairs(t) do
    print(k, v)
end

案例:

> ar = {'a','b'}
> for key,value in pairs(ar) do
>> print(key,value)
>> end
1	a
2	b
> 

无状态迭代器

无状态的迭代器是指不保留任何状态的迭代器,因此在循环中我们可以利用无状态迭代器避免创建闭包花费额外的代价。

菜鸟案例:

function iter (a, i)
    i = i + 1
    local v = a[i]
    if v then
       return i, v
    end
end
 
function ipairs (a)
    return iter, a, 0
end

多状态迭代器

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

菜鸟案例:

array = {"Google", "Runoob"}

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 table

在Lua中,没有专门的数组和字典,都是使用table表来实现的。

table的构造

构造器是创建和初始化表的表达式,表是lua特有的东西,最简单的构造函数就是{}。

> -- 创建table
> dict = {}
> -- 修改table中name的值,没有就添加
> dict['name'] = 'liling'
> -- 删除dict,内存回收
> dict = nil

table的操作

table常用方法:

方法 备注
table.concat (table [, sep [, start [, end]]]) 列出参数中指定table的数组部分从start位置到end位置的所有元素, 元素间以指定的分隔符(sep)隔开
table.insert (table, [pos,] value) 在table的数组部分指定位置(pos)插入值为value的一个元素. pos参数可选, 默认为数组部分末尾
table.remove (table [, pos]) 获取pos位置的元素,并删除它,默认删除最后一个
table.sort (table [, comp]) 给table排序

table连接

案例:

> t = {'a','b','c','d'}
> return print(table.concat(t))
abcd
> return print(table.concat(t,','))
a,b,c,d
> return print(table.concat(t,',',2,3))
b,c

插入

案例:

> t = {}
> table.insert(t,'a')
> t
table: 0x7fe487805180
> return print(table.concat(t,','))
a
> return table.insert(t,2,'xax')
> return print(table.concat(t,','))
a,xax

删除

案例:

> t = nil
> t = {'a','b'}
> return print(table.concat(t,','))
a,b
> table.remove(t,1)
a
> return print(table.concat(t,','))
b

排序

案例:

> fruits = {"banana","orange","apple","grapes"}
> table.sort(fruits)
> return table.concat(fruits,',')
apple,banana,grapes,orange

Lua 模块包

模块和封装库一般,Lua中可以将一些公用的代码放在一个文件中,以api形式在其它地方进行调用,降低代码耦合度。

创建模块

Lua的模块是变量、函数等元素组成的table,一个模块就是一个table,下面创建一个简单的lua模块:

cmath = {}
cmath.name = '自建Lua测试模块'
function cmath.max(i,j)
  if (i>j) then
    return i
  end
  return j
end
local function sum(i,j)
  return i+j
end
function cmath.sum(i,j)
  return sum(i,j)
end
return cmath

require 导入

在模块完成之后,我们需要在其它地方进行使用。这时需要先将该模块导入之后,才能调用模块内容。

案例:

#!/usr/local/bin/lua
require("module")
print(cmath.name)
print(cmath.max(5,7))
print(cmath.sum(1,4))

运行结果:

liuxionghui@bogon lua_code % lua test.lua
自建Lua测试模块
7
5

同时,我们可以将导入的模块赋值给一个变量,来进行调用,运行结果和前面是一样的:

#!/usr/local/bin/lua
x = require("module")
print(x.name)
print(x.max(5,7))
print(x.sum(1,4))

Lua Metatable元表

Lua中的table表可以通过key来获取相应value,但是它无法直接对两个表进行操作,比如两表相加。

而Metatable元表就是lua为了处理这种问题而出现的,它可以改变table的行为。

当Lua试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫"__add"的字段,若找到,则调用对应的值。"__add"等即时字段,其对应的值(往往是一个函数或是table)就是"元方法"。简单来说,就是每一种运算符,都会去寻找相应的字段,具体的实现操作就是其对应的值实现的。

这里只简单记录一下:

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

案例:

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

__index元方法

这是Lua原表中使用最多的一个元方法了,是获取Lua表中元素时会使用到的元方法,获取元素规则:

  • 1.在表中查找元素,如果找到就返回,没有找到的话继续
  • 2.判断表是否有元表,元表有没有__index元方法,没有就返回,有就继续
  • 3.如果__index方法是一个表,按1-3的逻辑继续,如果是一个函数,返回函数返回值。

Lua协同coroutine

协同是什么?

协同和线程相似但是又不同,它和线程一样拥有自己独立的堆栈、独立的局部变量和独立的指令指针,同时可以和其它协同共享全局变量。

不同之处在于,线程可以在同一时刻同时运行,而协同在一个时刻,只能一个在运行。

协同方法

方法 备注
create 创建协同
resume 启动协同
yield 挂起协同
status 查看协同状态
wrap 生成协同,功能与create重复
running 获取一个运行中的协同的id

案例:

co = coroutine.create(
    function(i)
        print(i);
    end
)
 
coroutine.resume(co, 1)   -- 1
print(coroutine.status(co))  -- dead

Lua 文件IO

Lua IO库用于读取处理文件,分为简单模式和完全模式。

  • 简单模式:拥有一个当前输入文件和输出文件,提供对于这些文件相关的操作。
  • 完全模式:使用外部文件句柄实现,以面向对象的形式将所有文件操作定义为句柄方法。

操作mode

和C语言一样,文件操作的模式有很多种:

  • r:只读模式,文件必须存在
  • w:打开只写文件,会清空文件内容,不存在的话自动创建
  • a:打开只写文件,保留原数据,在原数据后面进行添加,不存在的话自动创建
  • r+:可读写文件,文件必须存在
  • w+:可读写文件,会清空文件内容,不存在的话自动创建
  • a+:可读写文件,与a相似
  • b:二进制模式
  • +:添加+号,表示可读写

简单模式

简单模式使用标准的 I/O 或使用一个当前输入文件和一个当前输出文件。
案例:

-- 以只读方式打开文件
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)

操作方法

方法 备注
read("*n") 读取一个数字并返回它
read("*a") 从当前位置读取整个文件
read("*l") read默认值,读取下一行,在文件尾 (EOF) 处返回 nil
read(5) 返回一个指定字符个数的字符串,或在 EOF 时返回 nil
tmpfile() 返回一个临时文件句柄,该文件以更新模式打开,程序结束时自动删除
type(file) 检测obj是否一个可用的文件句柄
flush() 向文件写入缓冲中的所有数据
lines(optional file name) 返回一个迭代函数,每次调用将获得文件中的一行内容,当到文件尾时,将返回nil,但不关闭文件

完全模式

案例:

-- 以只读方式打开文件
file = io.open("test.lua", "r")
-- 输出文件第一行
print(file:read())
-- 关闭打开的文件
file:close()
-- 以附加的方式打开只写文件
file = io.open("test.lua", "a")
-- 在文件最后一行添加 Lua 注释
file:write("--test")
-- 关闭打开的文件
file:close()

完全模式的存在方法比简单模式稍多,这里不做记录。

Lua 异常处理

不管什么语言中,异常处理是一个不可缺少的东西。

断言

Lua断言很简单,assert方法,接收两个参数,第一个为false的时候,抛出异常,异常信息为第二个参数。

案例:

assert(type(12) == "number", "a 不是一个数字")
assert(type('123') == "number", "b 不是一个数字")

异常抛出

Lua异常抛出使用error方法,接收两个参数,第一个为message,即错误信息。level参数获取错误位置。通常情况下,error会附加一些错误位置的信息到message头部。

格式:

error (message [, level])

level参数:

  • 1:默认值,添加调用error的位置
  • 2:添加调用error函数的函数
  • 0: 不添加错误位置信息

异常捕获

Lua中捕获异常使用pcall和xpcall方法。

pcall

pcall接收一个函数和要传递给后者的参数,并执行,执行结果:有错误、无错误;返回值true或者或false, errorinfo。格式如下:

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

注意:pcall捕获异常时,会销毁了调用桟的部分内容,所以个人觉得,使用xpcall替换pcall。

xpcall

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

案例:

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

参考链接:菜鸟教程

作者:红雨
出处:https://www.cnblogs.com/52why
微信公众号: 红雨python
原文地址:https://www.cnblogs.com/52why/p/14578049.html