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设计背?蟮囊恍┚龆ê吞致郏梢圆慰家韵侣畚模嵌伎梢栽贚ua的网站上找到。
- R. Ierusalimschy, L. H. de Figueiredo, and W. Celes. Lua---an extensible extension language. Software: Practice & Experience 26 #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 Journal 21 #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
是一个保留字,但是 And
和 AND
是两个不一样的、但都合法的标识符。习惯上来说,以下划线开始且后面跟着大写字母的标识符 (例如 _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
´。
字符串也可以用双方括号来定界[[
· · · ]]
。这种括号方式的语法,字符串可以跨越多行,也可以包含嵌套的?辈换嶙迦魏涡蛄小7奖闫鸺笨嫉? `[[
´ 后面紧跟着一个换行符的话,这个换行符不会包括在字符串内。举个例子:在一个使用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, thread 和 table。 Nil 空类型只对应 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提供运行时的数字和字符串值得自动转换。任何对字符串的算术操作都会现尝试?炎址怀墒郑褂靡话愎嬖蜃弧7垂矗币桓鍪涤迷谛枰址牡胤绞保只嶙远怀勺址裱恢趾侠淼母袷健H绻付ㄊ等绾巫怀勺址胧褂米址庵械? 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中定义,也无法调用。我们在这里仅仅用来解释原理)。
所有的全局变量存在一个普通的Lua表中,称之为 环境变量表(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
显式语句块可以很好地控制变量的声明范围。显示语句块有时也常会在另一个语句块的中间添加 return 或 break 语句 (见 2.4.4)。
2.4.3 - 赋值
Lua允许多重赋值。因此,赋值的语法定义为:等号左边是一个变量表,右边是一个表达式表。两边的表中的元素都用逗号分隔开来:
stat ::= varlist1 `=´ explist1 varlist1 ::= var {`,´ var} explist1 ::= exp {`,´ exp}
在赋值之前,值的表长度会被 调整 为和变量的表一样。如果值比需要的多,多出的值就会被扔掉。如果值的数量不够,就会用足够多的 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
可以交换 x
和 y
的值。
对全局变量和表字段的赋值可以看作是通过元表进行的。对一个索引变量的赋值 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, while 和 repeat 具有通用的含义和类似的语法:
stat ::= while exp do block end stat ::= repeat block until exp stat ::= if exp then block { elseif exp then block} [else block] end
控制结构的条件表达式 exp 可以返回任意值。false 和 nil 都表示假。所有其他的值都认为是真(特别要说明的:数字0和空字符串也表示真)。
语句 return 用来从函数或者是语句段中返回一个值。函数和语句段都可以返回多个值,所以 return 语句的语法为:
stat ::= return [explist1]
break 语句可以用来终止while, repeat 或者 for 循环的执行,直接跳到循环后面的语句。
stat ::= break
break 结束最里面的一个循环。
由于语法的原因, return 和 break 语句只能作为语句块的 最后一个 语句。如果确实需要在语句块的中间使用 return 或者 break,需要使用一个显示语句块: `do return end
´ 和 `do break end
´,这样现在 return 和 break 就成为他们(内部)语句块中的最后一个语句了。实际上,这两种用法一般只用在调试中。
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.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中的关系运算符有
== ~= < > <= >=
这些运算只会产生 false 或 true值。
等于 (==
) 先比较操作数的类型。如果类型不一样,结果便是 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),所有的逻辑操作符认为 false 和 nil 都是假,其他的值都是真。
not 操作符总是返回 false 或 true。
合取运算 and 如果第一个参数是 false 或者 nil 则返回第一个参数;否则 and 返回第二个参数。析取运算 or 如果第一个参数不是 nil 或 false 则返回第一个参数,否则 or 返回第二个参数。 and 和 or 都使用截取计算,也就是,只有有必要的情况下才计算第二个参数。例如:
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
在函数调用中,首先会计算 prefixexp 和 args 。如果 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 ::= local function 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)是表达式的最终结果。同一个函数的不同的实例可以引用不同的外部局部变量也可以有不同的环境表。
形式参数(代表参数的变量,简称形参)就像用实际参数值(简称实参)初始化的局部变量一样。
parlist