• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

Lua 5.0 参考手册

原作者: [db:作者] 来自: [db:来源] 收藏 邀请

作者: Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes

Copyright © 2003 Tecgraf, PUC-Rio. All rights reserved.

译者:ShiningRay Nicholas @ NirvanaStudio

给予支持

1 - 绪论

Lua是一种为支持有数据描述机制的一般过程式编程语言而设计的扩展编程语言。它同样可以对面向对象语言、函数式程序设计(Functional Programming,如Lisp)以及数据驱动编程(data-driven programming)提供很好的支持。它的目标是被用作一种强大的、轻型的配置语言。Lua目前已经被实现为一个扩展库,是用clean C (ANSI C/C++的一个通用子集)编写的。

作为一个扩展语言,Lua没有"Main"函数的概念:它仅仅是嵌入一个宿主程序进行工作,可以称之为 嵌入式编程 或者简单的说是 宿主编程。这个宿主程序可以调用函数来执行Lua的代码片断,可以设置和读取Lua的变量,可以注册C函数让Lua代码调用。Lua的能力可以扩展到更大范围,在不同的领域内,这样就在同样的语法框架下创建了你自定义的编程语言。

Lua的发行版包括一个独立的嵌入式程序,lua,他使用Lua的扩展库来提供一个完全的Lua解释器。

Lua是自由软件,通常不提供任何担保,如它的版权说明中叙述的那样。 手册中描述的实现在Lua的官方网站可以找到,www.lua.org

如果需要知道Lua设计背后的一些决定和讨论,可以参考以下论文,它们都可以在Lua的网站上找到。

  • R. Ierusalimschy, L. H. de Figueiredo, and W. Celes. Lua---an extensible extension language. Software: Practice & Experience26 #6 (1996) 635-652.
  • L. H. de Figueiredo, R. Ierusalimschy, and W. Celes. The design and implementation of a language for extending applications. Proceedings of XXI Brazilian Seminar on Software and Hardware (1994) 273-283.
  • L. H. de Figueiredo, R. Ierusalimschy, and W. Celes. Lua: an extensible embedded language. Dr. Dobb's Journal21 #12 (Dec 1996) 26-33.
  • R. Ierusalimschy, L. H. de Figueiredo, and W. Celes. The evolution of an extension language: a history of Lua, Proceedings of V Brazilian Symposium on Programming Languages (2001) B-14-B-28.

Lua在葡萄牙语中的意思是“月亮”,发音是 LOO-ah。

2 - 语言

这一章将描述Lua的词法、语法和语义结构。换句话说,这一章会讲什么标记是合法的,他们是如何组合的,以及他们的组合是什么含义。

语言结构会使用常用的扩展BNF范式来解释,如{a} 表示0或多个a, [a] 表示a是可选的(0个或1个)。非终端字体(不能显示的)用 斜体表示,关键字是粗体,其他终端符号用typewriter(等宽)字体,并用单引号引出。

2.1 - 词法约定

Lua中的标识符(Identifiers)可以是任意的数字、字符和下划线“_”,但不能以数字开头。这条规则符合大多数编程语言中的标识符的定义。(字符的具体定义要根据系统的地区设置:任何区域设置可以认同的字母表中的字母都可以用在标识符中。)

下面的关键字(keywords)为保留关键字不可以作为标识符出现:

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

Lua对大小写敏感:and是一个保留字,但是 AndAND 是两个不一样的、但都合法的标识符。习惯上来说,以下划线开始且后面跟着大写字母的标识符 (例如 _VERSION) 是为Lua内部变量所保留的。

下面的字符(串)是其他的一些标记:

       +     -     *     /     ^     =
       ~=    <=    >=    <     >     ==
       (     )     {     }     [     ]
       ;     :     ,     .     ..    ...

字符串(Literal strings) 以单引号或者双引号定界,同时可以包含以下C语言风格的转义字符:

  • \a --- 铃声(bell)
  • \b --- 回退(backspace)
  • \f --- form feed
  • \n --- 新行(newline)
  • \r --- 回车(carriage return)
  • \t --- 水平制表符(horizontal tab)
  • \v --- 垂直制表符(vertical tab)
  • \\ --- 反斜杠(backslash)
  • \" --- 双引号(quotation mark)
  • \' --- 单引号(apostrophe)
  • \[ --- 左方括号(left square bracket)
  • \] --- 右方括号(right square bracket)

另外,一个 `\newline´ (一个反斜杠加上一个真正的换行符)会导致字符串内的分行。字符串中的字符也可以使用转义字符`\ddd´通过数字值来指定。ddd 是最多为3个十进制数字的序列。Lua中的字符串也可以包含8进制数字,包括嵌入零,它可以表示为 `\0´。

字符串也可以用双方括号来定界[[ · · · ]]。这种括号方式的语法,字符串可以跨越多行,也可以包含嵌套的,同时不会转义任何序列。方便起见,当开始的 `[[´ 后面紧跟着一个换行符的话,这个换行符不会包括在字符串内。举个例子:在一个使用ASCII编码(其中`a´ 的编码是 97,换行符是 10,字符`1´ 是 49)的系统中,以下四种格式得到的都是同一个字符串:

      (1)   "alo\n123\""
      (2)   '\97lo\10\04923"'
      (3)   [[alo
            123"]]
      (4)   [[
            alo
            123"]]

数值常量(Numerical constants) 可以有一个可选的底数部分和一个可选的指数部分。以下是有效的数值常量:

       3     3.0     3.1416  314.16e-2   0.31416E1

注释(Comments) 可以在任何地方出现,必须在最前面加上双减号 (--)。如果紧接着 -- 的文本不是 [[,那么会认为是一个 短注释(short comment), 这一行往后到行尾都是注释。否则,会认为是一个 常注释(long comment),注释直到相应的 ]]结束。长注释可以跨越多行,同时可以包含嵌套的 [[ · · · ]] 括号对。

为了方便起见,文件的第一行如果是以#开始,这个机制允许Lua在Unix系统中用做一个脚本解释器(见 6)。

2.2 - 值和类型

Lua是一种 动态类型语言(dynamically typed language)。这意味着变量是没有类型的;只有值才有。语言中没有类型定义。所有的值都包含他自身的类型。

Lua中有八种基本类型:nil, boolean, number, string, function, userdata, threadtableNil 空类型只对应 nil值,他的属性和其他任何值都有区别;通常它代表没有有效的值。 Boolean 布尔类型有两种不同的值 false and true。在Lua中, nil and false 代表成假条件;其他任何值都代表成真条件。 Number 数字类型表示实数(双精度浮点数)。(构建Lua解释器时也可以很容易地用其他内部的表示方式表示数字,如单精度浮点数或者长整型)。 String 字符串类型表示一个字符的序列。Lua 字符串可以包含8位字符,包括嵌入的 ('\0') (见 2.1)。

函数是Lua中的 第一类值(first-class values)。也就是说函数可以保存在变量中,当作参数传递给其他函数,或者被当作结果返回。Lua可以调用(和处理)Lua写的函数和C写的函数 (见 2.5.7)。

用户数据类型(userdata) 提供了让任意C数据储存在Lua变量中的功能。这种类型直接对应着一块内存,Lua中也没有任何预先定义的操作,除了赋值和一致性比较。然而,通过使用 元表(metatables),程序员可以定义处理userdata的操作。(见 2.8)。 Userdata 值不能在Lua中建立或者修改,只能通过 C API。这保证了宿主程序的数据完整性。

线程(thread) 类型代表了相互独立的执行线程,用来实现同步程序。

表(table) 类型实现了联合数组,也就是说,数组不仅可以使用数字,还能使用其他的值(除了 nil)。 而且,tables 可以是 互异的(heterogeneous),他们可以保存任何类型的值(除了 nil)。 Tables 是Lua中唯一的数据结构机制;他们可以用来表示一般数组,特征表,集合,记录,图,树等等。如果要表示记录,Lua使用字段名作为索引。语言支持 a.name 这种比较优美的表示方式,还有 a["name"]。在Lua中有几种建立表的简便方法 (见 2.5.6)。

就像索引一样,表字段的值也可以是任何类型(除了 nil)。特别需要注意地是,由于函数是第一型的值,表字段也可以包含函数。这样表也可以支持 方法(methods) (见 2.5.8)。

表,函数,和用户数据类型的值都是 对象(objects):变量不会包含他们的实际值,只是一个他们的引用(references)。 赋值,参数传递和函数返回只是操作这些值的引用,这些操作不会暗含任何拷贝。

库函数 type 返回一个字符串描述给出值所表示的类型 (见 5.1)。

2.2.1 - 类型转换

Lua提供运行时的数字和字符串值得自动转换。任何对字符串的算术操作都会现尝试把字符串转换成数字,使用一般规则转换。反过来,当一个数值用在需要字符串的地方时,数字会自动转换成字符串,遵循一种合理的格式。如果要指定数值如何转换成字符串,请使用字符串库中的 format 函数(见 5.3)。

2.3 - 变量

变量是储存值的地方。Lua中有三种不同的变量:全局变量,局部变量和表字段。

一个名称可以表示全局变量或局部变量(或者一个函数的正式参数,一种局部变量的特殊形式):

	var ::= Name

Lua假设变量是全局变量,除非明确地用local进行声明 (见 2.4.7)。局部变量有 词义范围(lexically scoped):局部变量可以被在它们范围内的函数自由访问 (见 2.6)。

在变量第一次赋值之前,它的值是 nil

方括号用于对表进行检索:

	var ::= prefixexp `[´ exp `]´

第一个表达式 (prefixexp)结果必须是表;第二个表达式 (exp) 识别表中一个特定条目。给出表的表达式有一个限制语法;详细见 2.5。

var.NAME 语法是 var["NAME"] 的较好形式:

	var ::= prefixexp `.´ Name

访问全局变量和表字段的实质可以通过元表进行改变。对索引变量 t[i] 的访问等同于调用 gettable_event(t,i)。(关于 gettable_event 的完整描述见 2.8。这个函数并没有在Lua中定义,也无法调用。我们在这里仅仅用来解释原理)。

所有的全局变量存在一个普?ǖ腖ua表中,称之为 环境变量表(environment tables) 或简称 环境(environments)。由C写的并导入到Lua中的函数 (C 函数) 全部共享一个通用 全局环境(global environment)。Lua写的每个函数 (a Lua 函数) 都有一个它自己的环境的引用,这样这个函数中的所有的全局变量都会指向这个环境变量表。当新创建一个函数时,它会继承创建它的函数的环境。要改变或者获得Lua函数的环境表,可以调用 setfenv or getfenv (见 5.1)。

访问全局变量 x 等同于 _env.x,又等同于

       gettable_event(_env, "x")

_env 是运行的函数的环境。(_env 变量并没有在Lua中定义。我们这里仅仅用来解释原理)

2.4 - 语句

Lua支持一种很通俗的语句集,和Pascal或者C中的很相似。他包括赋值,控制结构,过程调用,表构造和变量声明。

2.4.1 - 语句段

Lua执行的最小单元称之为一个 段(chunk)。一段语句就是简单的语句的序列,以顺序执行。每一个语句后面都可以加上一个分号(可选):

	chunk ::= {stat [`;´]}

Lua将语句段作为一个匿名函数 (见 2.5.8) 的本体进行处理。这样,语句段可以定义局部变量或者返回值。

一段语句可以储存在文件内或者宿主程序的一个字符串中。当语句段被执行时,他首先被预编译成虚拟机使用的字节码,然后虚拟机用一个解释器执行被编译的代码。

语句段也可以被预编译为二进制代码;详情参看 luac 程序。源代码和编译形态可以互相转换;Lua自动监测文件类型然后作相应操作。

2.4.2 - 语句块

一个语句块是一系列语句;从语句构成上来看,语句块等同于语句段:

	block ::= chunk

一个语句块可以明确定界来替换单个语句:

	stat ::= do block end

显式语句块可以很好地控制变量的声明范围。显示语句块有时也常会在另一个语句块的中间添加 returnbreak 语句 (见 2.4.4)。

2.4.3 - 赋值

Lua允许多重赋值。因此,赋值的语法定义为:等号左边是一个变量表,右边是一个表达式表。两边的表中的元素都用逗号分隔开来:

	stat ::= varlist1 `=´ explist1
	varlist1 ::= var {`,´ var}
	explist1 ::= exp {`,´ exp}

我们将在 2.5 讨论表达式。

在赋值之前,值的表长度会被 调整 为和变量的表一样。如果值比需要的多,多出的值就会被扔掉。如果值的数量不够,就会用足够多的 nil 来填充表直到满足数量要求。如果表达式表以一个函数调用结束,那么在赋值之前,函数返回的所有的值都会添加到值的表中(除非把函数调用放在括号里面;见 2.5)。

赋值语句首先计算出所有的表达式,然后才会执行赋值,所以代码:

       i = 3
       i, a[i] = i+1, 20

设置 a[3] 为 20,但不影响 a[4]。因为在 a[i] 中的 i 在赋值为4之前是等于3。同样的,下面这行:

       x, y = y, x

可以交换 xy 的值。

对全局变量和表字段的赋值可以看作是通过元表进行的。对一个索引变量的赋值 t[i] = val 等同于 settable_event(t,i,val)。 (settable_event详细介绍参看 2.8 ,Lua中并未定义该函数,他也无法直接调用。我们这里只是用它来进行解释。)

对全局变量的赋值 x = val 等同于赋值语句 _env.x = val,像前面也等同于:

       settable_event(_env, "x", val)

_env 是运行函数的环境。(_env 变量并未在Lua中定义。我们这里只是用来进行解释。)

2.4.4 - 控制结构

控制结构 if, whilerepeat 具有通用的含义和类似的语法:

	stat ::= while exp do block end

	stat ::= repeat block until exp
	stat ::= if exp then block {elseif exp then block} [else block] end

Lua也有 for 语句,有两种格式 (见 2.4.5)。

控制结构的条件表达式 exp 可以返回任意值。falsenil 都表示假。所有其他的值都认为是真(特别要说明的:数字0和空字符串也表示真)。

语句 return 用来从函数或者是语句段中返回一个值。函数和语句段都可以返回多个值,所以 return 语句的语法为:

	stat ::= return [explist1]

break 语句可以用来终止while, repeat 或者 for 循环的执行,直接跳到循环后面的语句。

	stat ::= break

break 结束最里面的一个循环。

由于语法的原因, returnbreak 语句只能作为语句块的 最后一个 语句。如果确实需要在语句块的中间使用 return 或者 break,需要使用一个显示语句块: `do return end´ 和 `do break end´,这样现在 returnbreak 就成为他们(内部)语句块中的最后一个语句了。实际上,这两种用法一般只用在调试中。

2.4.5 - For 语句

for 语句有两种形式:数值形式和一般形式。

数值形式的 for 循环根据一个控制变量用算术过程重复一语句块。语法如下:

	stat ::= for Name `=´ exp `,´ exp [`,´ exp] do block end

block 语句块根据 name 以第一个 exp 的值开始,直到他以第三个 exp 为步长达到了第二个 exp。一个这样的 for 语句:

       for var = e1, e2, e3 do block end

等价于一下代码:

       do
         local var, _limit, _step = tonumber(e1), tonumber(e2), tonumber(e3)
         if not (var and _limit and _step) then error() end
         while (_step>0 and var<=_limit) or (_step<=0 and var>=_limit) do
           block
           var = var + _step
         end
       end

注意:

  • 三种控制表达式只会被计算一次,在循环开始之前。他们的结果必须是数值。
  • _limit_step 是不可见的变量。这里只是为了进行解释。
  • 如果你在程序块内给 var 赋值,结果行为将会不确定。
  • 如果没有给出第三个表达式(步长),那么默认为1。
  • 你可以使用 break 来退出 for 循环。
  • 循环变量 var 是局部变量;你不可以在 for 循环结束之后继续使用。如果你需要使用这个值,请在退出循环之前把它们传给其他变量。

for 的语句的一般形式是操作于函数之上的,称之为迭代器(iterators)。每一个迭代过程,它调用迭代函数来产生新的值,直到新的值是 nil 。一般形式 for 循环有如下语法:

	stat ::= for Name {`,´ Name} in explist1 do block end

一个这样的 for 语句

       for var_1, ..., var_n in explist do block end

等同于以下代码:

       do
         local _f, _s, var_1 = explist
         local var_2, ... , var_n
         while true do
           var_1, ..., var_n = _f(_s, var_1)
           if var_1 == nil then break end
           block
         end
       end

注意:

  • explist 只会计算一次。他的结果是一个 迭代 函数,一个 状态,和给第一个 迭代变量的一个初始值。
  • _f_s 是不可见的变量。这里只是用来进行解释说明。
  • 如果你在语句块中给 var_1 赋值,那么行为就会变得不确定。
  • 你可以使用 break 来退出 for 循环。
  • 循环变量 var_i 是局部变量;你不可以在 for 循环结束之后继续使用。如果你需要使用这个值,请在退出循环之前把它们传给其他变量。

2.4.6 - 语句式函数调用

如果要忽略可能的影响,函数调用可以按照语句执行:

	stat ::= functioncall

I在这里,所有的返回值都会被忽略。函数调用将在 2.5.7 详细解释。

2.4.7 - 局部变量声明

局部变量可以在语句块中任何地方声明。声明时也可以添加一个初始赋值:

	stat ::= local namelist [`=´ explist1]
	namelist ::= Name {`,´ Name}

如果出现初始赋值,他的语法和多重赋值语句一样(见 2.4.3)。否则,所有的变量都会初始化为 nil

一个语句段也是一个语句块(见 2.4.1),所以语句段之内的任何显式语句块之外也可以声明局部变量。这种局部变量在语句段结束就会销毁。

局部变量的可见规则会在 2.6解释。

2.5 - 表达式

Lua中有以下几种基本表达式:

	exp ::= prefixexp
	exp ::= nil | false | true

	exp ::= Number
	exp ::= Literal
	exp ::= function
	exp ::= tableconstructor
	prefixexp ::= var | functioncall | `(´ exp `)´

数字和字符串已经在 2.1 中解释;变量在 2.3 中解释;函数定义在 2.5.8;函数调用在 2.5.7;表构造器在 2.5.6。

一个用括号括起的表达式只会返回一个值。这样,(f(x,y,z)) 将只会返回单一的一个值,即使 f 可以返回多个值,((f(x,y,z)) 的值将是 f 返回的第一个值或者如果 f 没有返回任何值就是 nil )。

表达式也可以使用各种算术运算符,关系运算符和逻辑运算符,下面几节就会讲到。

2.5.1 - 算术运算符

Lua支持常见的几种运算符:二元 + (加), - (减), * (乘), / (除), 以及 ^ (指数运算); 一元 - (负号)。如果操作数是数字,或者是可以转换成数字的字符串(见 2.2.1),那么所有的操作都和算术意义上的运算一致(除了指数)。指数运算其实是调用一个全局函数 __pow,否则一个合适的元方法将会被调用(见 2.8)。标准数学库定义了函数 __pow,给出了指数运算的定义(见 5.5)。

2.5.2 - 关系运算符

Lua中的关系运算符有

       ==    ~=    <     >     <=    >=

这些运算只会产生 falsetrue值。

等于 (==) 先比较操作数的类型。如果类型不一样,结果便是 false。否则,再比较操作数的值。对象(表,用户数据,线程,和函数)是按照引用进行比较:只有两个对象是同一个对象的时候,才认为是相等。每次你创建一个新的对象(表,用户数据,或者是函数)。这个新的对象将不同于前面存在的任何对象。

你可以用"eq"元方法改变Lua比较表的方式(见 2.8)。

2.2.1 的转换规则 不适用 于相等比较。这样," "0"==0 结果是 false ,同样 t[0]t["0"] 给出的是表中不同的字段。

而操作符 ~= 是等于 (==) 的相反的操作。

T操作符的执行顺序如下。如果两个参数都是数字,那么它们就直接进行比较。如果,两个参数都是字符串,那么它们的值会根据当前的区域设置进行比较。否则,Lua尝试调用"lt"或者 "le" 元方法(见 2.8)。

2.5.3 - 逻辑运算符

Lua中的逻辑运算符是:

       and   or    not

和控制结构一样(见 2.4.4),所有的逻辑操作符认为 falsenil 都?羌伲渌闹刀际钦妗?

not 操作符总是返回 falsetrue

合取运算 and 如果第一个参数是 false 或者 nil 则返回第一个参数;否则 and 返回第二个参数。析取运算 or 如果第一个参数不是 nilfalse 则返回第一个参数,否则 or 返回第二个参数。 andor 都使用截取计算,也就是,只有有必要的情况下才计算第二个参数。例如:

       10 or error()       -> 10
       nil or "a"          -> "a"
       nil and 10          -> nil
       false and error()   -> false
       false and nil       -> false
       false or nil        -> nil
       10 and 20           -> 20

2.5.4 - 串联接

在Lua中字符串连接操作符是两个点 (`..´)。如果两边的操作数都是字符或者数字,他们就都会按照 2.2.1的规则被转换成字符串。否则,将调用 "concat" 元方法(见 2.8)。

2.5.5 - 优先级

Lua中的操作符的优先级如下表所示,从低到高优先级:

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

表达式中,你可以使用括号来改变优先顺序。串联接符 (`..´) 和指数符 (`^´) 都是右结合的。其他二元操作都是左结合的。

2.5.6 - 表构造器

表构造器是创建表的表达式。当计算构造器的时候,就会创建一个新的表。构造器可以用来创建空的表,或者创建表并初始化一些字段。一般的语法如下:

	tableconstructor ::= `{´ [fieldlist] `}´
	fieldlist ::= field {fieldsep field} [fieldsep]
	field ::= `[´ exp `]´ `=´ exp | Name `=´ exp | exp
	fieldsep ::= `,´ | `;´

[exp1] = exp2 形式的每一个添加到新表中的字段条目以 exp1 为键并以 exp2 为值。name = exp 形式的字段,等同于 ["name"] = exp。最后,exp 形式的字段等同于 [i] = exp 其中 i 是连续的整数,从1开始。其它格式的字段不会影响它的计数。例如:

       a = {[f(1)] = g; "x", "y"; x = 1, f(x), [30] = 23; 45}

等同于:

       do
         local temp = {}
         temp[f(1)] = g
         temp[1] = "x"         -- 1st exp
         temp[2] = "y"         -- 2nd exp
         temp.x = 1            -- temp["x"] = 1
         temp[3] = f(x)        -- 3rd exp
         temp[30] = 23
         temp[4] = 45          -- 4th exp
         a = temp
       end

如果列表中最后一个字段的形式是 exp 同时表达式又是一个函数调用,那么调用返回的所有值会依次进入列表(见 2.5.7)。如果要避免这种情况,在函数调用两边加上括号(见 2.5)。

字段列表可以有一个结尾的分隔符,这个对由机器生成的列表十分方便。

2.5.7 - 函数调用

Lua中的一个函数调用有如下语法:

	functioncall ::= prefixexp args

在函数调用中,首先会计算 prefixexpargs 。如果 prefixexp 的值是 function 类型,那么那个函数就会被调用,同时使用给出的参数。否则,他的 "call" 元方法就会被调用,第一个参数是 prefixexp 的值,接下来是原来的调用参数(见 2.8)。

形式

	functioncall ::= prefixexp `:´ Name args

可以用来调用“方法”("methods")。调用 v:name(...) 语法上比 v.name(v,...),要好一些,除非表达式 v 只计算一次。

参数可以有以下几种语法:

	args ::= `(´ [explist1] `)´
	args ::= tableconstructor
	args ::= Literal

所有的参数表达式都会在实际调用之前进行计算。f{...} 的调用形式在语法上较 f({...}) 要好,是因为,参数列表示一个单独的新表。 f'...' (或者 f"..." 或者 f[[...]]) 较 f('...') 要好,是因为参数列表是一个单独的字符串。

因为函数可以返回任意个结果(见 2.4.4),结果的数量必须在使用它们前进行调整。如果函数按照语句进行调用(见 2.4.6),那么它的返回列表就会被调整为零个元素,这样就舍弃了所有的返回值。如果调用函数时,他是一个表达式列表的最后一个元素,那么不会做调整(除非调用时加了括号)。

以下是一些例子:

       f()                -- 调整为0个结果
       g(f(), x)          -- f() 被调整成1个结果
       g(x, f())          -- g 获得 x 加上f()返回的所有值
       a,b,c = f(), x     -- f() 被调整成1个结果(此时c获得nil值)
       a,b,c = x, f()     -- f() 被调整为两个结果
       a,b,c = f()        -- f() 被调整为3个结果
       return f()         -- 返回所有 f() 返回的值
       return x,y,f()     -- 建立一个表包含所有 f() 返回的值
       {f()}              -- creates a list with all values returned by f()
       {f(), nil}         -- f() 被调整为一个结果

如果你用括号括起调用的函数,那么它就会被调整为返回一个值。

       return x,y,(f())   -- returns x, y, and the first value from f()
       {(f())}            -- creates a table with exactly one element

作为Lua语法自由格式的一个例外,你不能在函数调用的 `(´ 前面加入一个换行。这个限制可以避免语言中的一些二义性。如果你写:

       a = f
       (g).x(a)

Lua会读作 a = f(g).x(a)。这样,如果你想执行为两条语句,你必须在中间加分号。如果你实际上想调用 f,你就必须删除 (g) 前面的换行。

return functioncall 的调用格式称之为 尾部调用(tail call)。Lua实现了proper tail calls;在一个尾部调用中,被调用的函数将会重新使用调用程序的栈。因此,程序执行对嵌套尾部调用的次数没有任何限制。然而,尾部调用会清楚调用函数的调试信息。注意尾部调用只有在特殊的语法中才能出现,也就是 return 只有一个函数调用作为参数,这种语法保证了调用函数确切返回被调用函数的返回值。所以,下面的例子都不是尾部调用:

  return (f(x))        -- results adjusted to 1
  return 2 * f(x)
  return x, f(x)       -- additional results
  f(x); return         -- results discarded
  return x or f(x)     -- results adjusted to 1

2.5.8 - 函数定义

函数定义的语法是:

	function ::= function funcbody
	funcbody ::= `(´ [parlist1] `)´ block end

下面较好的语法简化了函数定义:

	stat ::= function funcname funcbody
	stat ::= localfunction Name funcbody
	funcname ::= Name {`.´ Name} [`:´ Name]

语句

       function f () ... end

会被翻译为

       f = function () ... end

语句

       function t.a.b.c.f () ... end

会被翻译为

       t.a.b.c.f = function () ... end

语句

       local function f () ... end

会被翻译为

       local f; f = function () ... end

一个函数定义是一个可执行的表达式,他的类型为 函数(function) 。当Lua预编译语句段的时候,他的函数体也会被预编译。这样,当Lua执行函数定义的时候,函数被 实例化封装 closed)。这个函数实例(或闭包 closure)是表达式的最终结果。同一个函数的不同的实例可以引用不同的外部局部变量也可以有不同的环境表。

形式参数(代表参数的变量,简称形参)就像用实际参数值(简称实参)初始化的局部变量一样。

	parlist1 ::= namelist [`,´ `...´]
	parlist1 ::= `...´

当调用一个函数时,实参表会调整为和形参一样的长度,除非函数是 variadic 或者 变长参数函数(vararg function)。变长参数函数在其参数列表最后有三个点 (`...´)。 变长参数函数不会对参数列表进行调整;而是,它把所有的额外实参放到一个隐含的形参 arg中。 arg 的值是一个表,包含一个字段 `n´ 表示额外参数的个数,位置 1, 2, ..., n是额外的参数。

请思考以下函数定义的例子:

       function f(a, b) end
       function g(a, b, ...) end
       function r() return 1,2,3 end

然后,我们有以下实参到形参的对应关系:

       CALL            PARAMETERS

       f(3)             a=3, b=nil
       f(3, 4)          a=3, b=4
       f(3, 4, 5)       a=3, b=4
       f(r(), 10)       a=1, b=10
       f(r())           a=1, b=2

       g(3)             a=3, b=nil, arg={n=0}
       g(3, 4)          a=3, b=4,   arg={n=0}
       g(3, 4, 5, 8)    a=3, b=4,   arg={5, 8; n=2}
       g(5, r())        a=5, b=1,   arg={2, 3; n=2}

结果使用 return 语句返回(见 2.4.4)。如果控制到达了函数尾部而没有遇到 return 语句,那么函数没有返回值。

冒号(:) 语法是用来定义 methods 的,也就是,函数有一个隐含的额外参数 self. 。这样,语句:

       function t.a.b.c:f (...) ... end

相对以下是较好的形式:

       t.a.b.c.f = function (self, ...) ... end

2.6 - 可见性规则

Lua是一个有词法范围的语言。变量的范围从声明语句后的第一个语句开始到包含声明的最内部的语句块为止。例如:

  x = 10                -- global variable
  do                    -- new block
    local x = x         -- new `x', with value 10
    print(x)            --> 10
    x = x+1
    do                  -- another block
      local x = x+1     -- another `x'
      print(x)          --> 12
    end
    print(x)            --> 11
  end
  print(x)              --> 10  (the global one)

注意:在类似 local x = x,正在声明的新的 x 尚未进入范围,所以第二个 x 指代的是外面的变量。

由于词法范围的规则,在局部变量的范围内定义的函数可以任意访问这些变量。例如:

  local counter = 0
  function inc (x)
    counter = counter + x
    return counter
  end

内部函数使用的局部变量在函数内部称之为 上值(upvalue),或者 外局部变量(external local variable)

注意每个 local 语句执行时会定义一个新的局部变量。看以下例子:

  a = {}
  local x = 20
  for i=1,10 do
    local y = 0
    a[i] = function () y=y+1; return x+y end
  end

循环产生了十个闭包(也就是,十个匿名函数的实例)。每个闭包使用不同的 y 变量,但他们共享同一个 x 变量。

2.7 - 错误处理

因为Lua是一个扩展语言,所有的Lua动作都是从宿主程序中调用Lua库中函数的C代码开始的(见 3.15)。无论错误发生在Lua编译过程时或执行时,控制返回C,然后可以做相应的处理(比如打印一个错误)。

Lua代码可以通过调用error函数来产生一个错误(见 5.1)。如果你要在Lua中捕获错误,你可以使用 pcall 函数(见 5.1)。

2.8 - 元表 (Metatables)

Lua中的每一个表和用户数据都可以拥有一个 元表(metatable)。这个 元表 是一个普通的Lua表,定义了在特定操作下原始表和用户数据的行为。你可以通过设置一个对象的元表中的特定字段来更改它某些方面的行为。例如,当一个对象是一个加法的操作数时,Lua检查它的元表中的 "__add" 字段是不是一个函数。如果是,Lua调用它来执行加法。

我们称元表中的键(字段名,key)为 事件(events) ,值为 元方法(metamethods)。在上一个例子中, "add" 是事件,执行加法的函数是元方法。

你可以通过 set/getmetatable 函数来查询和更改一个对象的元表(见 5.1)。

元表可以控制对象在算术操作、比较、串连接、索引取值中如何运行。元表也可以定义一个函数当收集内存垃圾时调用。每一个操作这里Lua都用一个特定的键关联,称之为事件。当Lua对一个表或是一个用户数据执行上面中的一个操作时,它先检查元表控制的操作已经罗列在下面。每个操作有一个相应的名称,代表了他的含义。他们在元表中的键是由名称前加上两条下划线;如,操作 "add" 的键是 "__add"。这些操作的语义

这里给出的Lua代码仅仅是说明性的;真正的行为是硬编码在解释器中的,比下面的的模拟的效率要高很多。描述中用到的函数 (rawget, tonumber, 等等) 在 5.1 中会对他们进行描述。特别地,要获得一个给定对象的元方法,我们使用这个表达式:

  metatable(obj)[event]

这个要读作:

  rawget(metatable(obj) or {}, event)

也就是,访问元方法时不会调用其它元方法,同时调用没有元表的对象不会出错(它返回一个 nil值)。

  • "add": + 加法操作。

    下面的 getbinhandler 函数定义了Lua如何给一个二元操作选择一个处理器。首先,Lua尝试第一个操作数。如果它的类型没有定义这个操作的处理器,那么然后Lua尝试第二个操作数。

     function getbinhandler (op1, op2, event)
       return metatable(op1)[event] or metatable(op2)[event]
     end
    

    利用该函数,op1 + op2 的行为方式可看作是

     function add_event (op1, op2)
       local o1, o2 = tonumber(op1), tonumber(op2)
       if o1 and o2 then  -- both operands are numeric?
         return o1 + o2   -- `+' here is the primitive `add'
       else  -- at least one of the operands is not numeric
         local h = getbinhandler(op1, op2, "__add")
         if h then
           -- call the handler with both operands
           return h(op1, op2)
         else  -- no handler available: default behavior
           error("...")
         end
       end
     end
    
  • "sub": - 操作。行为方式类似 "add" 操作。
  • "mul": * 操作。行为方式类似 "add" 操作。
  • "div": / 操作。行为方式类似 "add" 操作。
  • "pow": ^ (指数) 操作

     function pow_event (op1, op2)
       local o1, o2 = tonumber(op1), tonumber(op2)
       if o1 and o2 then  -- both operands are numeric?
         return __pow(o1, o2)   -- call global `__pow'
       else  -- at least one of the operands is not numeric
         local h = getbinhandler(op1, op2, "__pow")
         if h then
           -- call the handler with both operands
           return h(op1, op2)
         else  -- no handler available: default behavior
           error("...")
         end
       end
      end
    
  • "unm": 一元取负 - 操作。

     function unm_event (op)
       local o = tonumber(op)
       if o then  -- operand is numeric?
         return -o  -- `-' here is the primitive `unm'
       else  -- the operand is not numeric.
         -- Try to get a handler from the operand
         local h = metatable(op).__unm
         if h then
           -- call the handler with the operand and nil
           return h(op, nil)
         else  -- no handler available: default behavior
           error("...")
         end
       end
     end
    
  • "concat": .. (串连接)操作。

     function concat_event (op1, op2)
       if (type(op1) == "string" or type(op1) == "number") and
          (type(op2) == "string" or type(op2) == "number") then
         return op1 .. op2  -- primitive string concatenation
       else
         local h = getbinhandler(op1, op2, "__concat")
         if h then
           return h(op1, op2)
         else
           error("...")
         end
       end
     end
    
  • "eq": == 操作。函数 getcomphandler 定义了Lua是如何为比较操作选择一个元方法的。只有当参与比较的两个对象属于同一类型而且需要的元方法一样时,才会选择这个元方法。

     function getcomphandler (op1, op2, event)
       if type(op1) ~= type(op2) then return nil end
       local mm1 = metatable(op1)[event]
       local mm2 = metatable(op2)[event]
       if mm1 == mm2 then return mm1 else return nil end
     end
    

    事件如下定义:

     function eq_event (op1, op2)
       if type(op1) ~= type(op2) then  -- different types?
         return false   -- different objects
       end
       if op1 == op2 then   -- primitive equal?
         return true   -- objects are equal
       end
       -- try metamethod
       local h = getcomphandler(op1, op2, "__eq")
       if h then
         return h(op1, op2)
       else
         return false
       end
     end
    

    a ~= b is equivalent to not (a == b).

  • "lt": < 操作。

     function lt_event (op1, op2)
       if type(op1) == "number" and type(op2) == "number" then
         return op1 < op2   -- numeric comparison
       elseif type(op1) == "string" and type(op2) == "string" then
         return op1 < op2   -- lexicographic comparison
       else
         local h = getcomphandler(op1, op2, "__lt")
         if h then
           return h(op1, op2)
         else
           error("...");
         end
       end
     end
    
    

    a > b is equivalent to b < a.

  • "le": <= 操作。

     function le_event (op1, op2)
       if type(op1) == "number" and type(op2) == "number" then
         return op1 <= op2   -- numeric comparison
       elseif type(op1) == "string" and type(op2) == "string" then
         return op1 <= op2   -- lexicographic comparison
       else
         local h = getcomphandler(op1, op2, "__le")
         if h then
           return h(op1, op2)
         else
           h = getcomphandler(op1, op2, "__lt")
           if h then
             return not h(op2, op1)
           else
             error("...");
           end
         end
       end
     end
    

    a >= b is equivalent to b <= a. Note that, in the absence of a "le" metamethod, Lua tries the "lt", assuming that a <= b is equivalent to not (b < a).

  • "index": 通过索引访问 table[key]

     function gettable_event (table, key)
       local h
       if type(table) == "table" then
         local v = rawget(table, key)
         if v ~= nil then return v end
         h = metatable(table).__index
         if h == nil then return nil end
       else
         h = metatable(table).__index
         if h == nil then
           error("...");
         end
       end
       if type(h) == "function" then
         return h(table, key)      -- call the handler
       else return h[key]          -- or repeat operation on it
     end
    
  • "newindex": 给表的索引赋值 table[key] = value

     function settable_event (table, key, value)
       local h
       if type(table) == "table" then
         local v = rawget(table, key)
         if v ~= nil then rawset(table, key, value); return end
         h = metatable(table).__newindex
         if h == nil then rawset(table, key, value); return end
       else
         h = metatable(table).__newindex
         if h == nil then
           error("...");
         end
       end
       if type(h) == "function" then
         return h(table, key,value)    -- call the handler
       else h[key] = value             -- or repeat operation on it
     end
    
    
  • "call": 当Lua调用某个值时调用。

     function function_event (func, ...)
       if type(func) == "function" then
         return func(unpack(arg))   -- primitive call
       else
         local h = metatable(func).__call
         if h then
           return h(func, unpack(arg))
         else
           error("...")
         end
       end
     end
    

2.9 - 垃圾收集

Lua 会自动进行内存管理。这意味着你不需要担心新对象的内存分配问题,也不需要释放不用的对象。Lua 通过不断地运行 垃圾收集器 收集 dead objects (也就是那些Lua中无法访问的对象)来自动管理内存。Lua中所有的对象都是自动管理的目标:表,用户数据,函数,线程,和字符串。Lua使用两个数字控制垃圾收集循环。一个数字表示Lua使用的动态内存的字节数,另一个是阀值。当内存字节数到达阀值时,Lua就运行垃圾收集器,来释放死对象的空间。一旦字节计数器被调整,那么阀值就会被设为字节计数器新值的两倍。

通过C API,你可以查询和更改阀值(见 3.7)。将阀值设为零时会强制立刻进行垃圾收集,同时把他设为足够大就可以停止垃圾收集。仅使用Lua代码中的 gcinfocollectgarbage 函数 (见 5.1)可以获得一定程度上对垃圾收集循环的控制。

2.9.1 - 垃圾收集元方法 (Garbage-Collection Metamethods)

使用 C API,你可以对用户数据设置一个垃圾收集元方法(见 2.8)。这些元方法也称为 终结器(finalizers)。终结器允许你用外部的资源管理来调整Lua的垃圾收集(如关闭文件,网络或数据库连接,或者释放你自己的内存。

用元表中包含 __gc 字段的自由用户数据不会立即被垃圾收集器回收。而是,Lua把它们放在一个列表中。收集完毕之后,Lua会对这个列表中的用户数据执行和以下函数相等的操作:

 function gc_event (udata)
   local h = metatable(udata).__gc
   if h then
     h(udata)
   end
 end

在每个垃圾收集过程最后,调用用户数据的终结器的顺序,将按照他们在收集过程中添加到列表中的相反顺序进行。也就是,第一个被调用的终结器是和在程序中创建的最后一个用户数据相关的那个终结器。

2.9.2 - 弱表

一个 弱表(weak table) 是一个包含的元素是 弱引用(weak references)的表。垃圾收集器会忽略弱引用。换句话说,如果指向一个对象的引用只有弱引用,那么这个对象还是要被垃圾收集器回收。

弱表可以包含弱的键,弱的值,或者两者皆有。一个包含弱键的表允许它的键被回收,但值不可以。一个同时包含弱键和弱值的表允许键和值的回收。无论哪种情况,只要键或者值中的一个被回收了,那么这一对键值将会从表中删除。这个表的弱属性是由它的元表的 __mode 字段控制的。如果 __mode 字段是一个包含字符 `k´的字符串,那么表中的键是弱键。如果 __mode 字段是一个包含字符 `v´ 的字符串,那么表中的值是弱值。

在你将表用作元表之后,你不应该更改 __mode 字段的值。否则,这个元表控制的表的弱表行为将会不确定。

2.10 - 同步程序

Lua支持同步程序,也称为 半同步程序(semi-coroutines)协同多线程(collaborative multithreading)。Lua中的一个同步程序代表了一个独立的执行线程。然而,不像在多线程系统中的线程那样,一个同步程序只有在调用了一个yield(产生结果)函数才能挂起它的执行。

你可以调用 coroutine.create 来创建一个同步程序。它唯一的一个参数是一个函数,代表同步程序的主函数。create 函数仅仅建立一个新的同步程序然后返回一个它的句柄 (一个线程 thread 类型的对象);它不会启动该同步程序。

当你第一次调用 coroutine.resume,将 coroutine.create 返回的线程对象作为第一个参数传递给它,然后同步程序就启动了,从它的主函数的第一行开始。传给 coroutine.resume 的额外的参数会作为同步程序主函数的参数传递过去。在同步程序开始执行之后,它一直运行到它结束或产生结果。

一个同步程序通过两种方式结束它的运行:正常情况下,当它的主函数返回(显式地或隐式的,在最后一个指令之后)时结束;异常地,如果有未保护的错误。第一各情况下,coroutine.resume 返回 true,加上同步程序主函数返回的其它值。在有错误的情况下,coroutine.resume 返回 false ,并附上错误信息。

一个同步程序通过调用 coroutine.yield 来产生结果。当一个同步程序产生结果,相应的 coroutine.resume 就立刻返回,即使操作发生在嵌套函数调用中(也就是,不在主函数中,而在被主函数直接或间接调用的函数中)。在这种情况下, coroutine.resume 也返回 true,以及传给 coroutine.yield。的所有参数。下次你继续同一个同步程序时,它会从它原来yield的地方继续执行,而 coroutine.yield 将返回给主程序传给 coroutine.resume 的额外参数。

coroutine.wrap 函数创建一个和 coroutine.create 一样的同步程序,但它不返回同步程序本身,而是返回一个继续同步程序的函数(当调用的时候)。传递给这个函数的参数作为继续resume的额外参数。函数将返回resume返回的所有值,出除了第一个(布尔值的错误代码)。不像 coroutine.resume,这个函数不捕获错误;出现任何错误都传回给调用者。

请考虑以下例子:

function foo1 (a)
  print("foo", a)
  return coroutine.yield(2*a)
end

co = coroutine.create(function (a,b)
      print("co-body", a, b)
      local r = foo1(a+1)
      print("co-body", r)
      local r, s = coroutine.yield(a+b, a-b)
      print("co-body", r, s)
      return b, "end"
end)
       
a, b = coroutine.resume(co, 1, 10)
print("main", a, b)
a, b, c = coroutine.resume(co, "r")
print("main", a, b, c)
a, b, c = coroutine.resume(co, "x", "y")
print("main", a, b, c)
a, b = coroutine.resume(co, "x", "y")
print("main", a, b)

当你运行它的时候,它会产生以下输出结果:

co-body 1       10
foo     2
main    true    4
co-body r
main    true    11      -9
co 

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Lua函数声明与调用发布时间:2022-07-22
下一篇:
drizzle lua 数据传递(ngx.location.capture)发布时间:2022-07-22
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap