©
本文檔使用 php中文網手册 發布
本节介绍 Lua 的词汇,语法和语义。换句话说,本节描述哪些标记是有效的,它们如何组合以及它们的组合意味着什么。
将使用通常的扩展BNF符号来解释语言构造,其中{ a }表示0或更多a,并且a表示可选的a。非终端显示为非终端,关键字显示为kword,其他终端符号显示为' = '。Lua的完整语法可以在本手册末尾的§9中找到。
Lua 是一种自由形式的语言。它忽略空格(包括新行)和词法元素(标记)之间的注释,除了名称和关键字之间的分隔符。
Lua中的名称(也称为标识符)可以是任何字母,数字和下划线的字符串,不是以数字开始,也不是保留字。标识符用于命名变量,表格字段和标签。
以下关键字是保留的,不能用作名称:
and break do else elseif endfalse for function goto if inlocal nil not or repeat returnthen true until while
Lua是一种区分大小写的语言:并且是一个保留字,但And和AND是两个不同的有效名称。 作为惯例,程序应该避免创建以下划线开头的名称,后跟一个或多个大写字母(例如_VERSION)。
以下字符串表示其他令牌:
+ - * / % ^ #& ~ | << >> //== ~= <= >= < > =( ) { } [ ] ::; : , . .. ...
甲短文本字符串可以通过匹配单引号或双引号进行分隔,并且可以包含下面的C状的转义序列:“ \a
”(钟形),“ \b
”(退格),“ \f
”(形式进料),“ \n
”(换行), ' \r
'(回车符),' \t
'(水平制表符),' \v
'(垂直制表符),' \\
'(反斜杠),' \"
'(引号双引号)和' \'
'(撇号单引号)。反斜杠后跟换行符会导致字符串中出现换行符。转义序列'\z
'跳过以下的空白字符,包括换行符; 将长文字字符串分割并缩进多行而不将新行和空格添加到字符串内容中特别有用。一个简短的文字字符串不能包含未转义的换行符,也不能包含不构成有效转义序列的转义符。
我们可以通过数字值(包括嵌入的零)在短文字字符串中指定任何字节。这可以通过转义序列来完成\xXX
,其中XX是正好两个十六进制数字的序列,或者是转义序列\ddd
,其中ddd是最多三位十进制数字的序列。(请注意,如果十进制转义序列后跟一个数字,则必须使用三位数字表示。)
Unicode字符的UTF-8编码可以用转义序列(注意强制括号括起来)插入到一个文字字符串中,其中XXX是一个代表字符代码点的一个或多个十六进制数字的序列。\u{
XXX
}
文字字符串也可以使用由长括号括起来的长格式来定义。我们定义了一个开放的n级开放长方括号作为开放方括号,其后是n等号,然后是另一个开放方括号。因此,0级[[
开放长括号写为[=[
,1级开放长括号写为,等等。甲长括号被类似地定义; 例如,第4级的一个结束长括号写为]====]
。一个长的文字从任何级别的开放式长支架开始,到同级别的第一个长支架结束。它可以包含除了相同级别的右括号之外的任何文本。括号内的文字可以运行多行,不解释任何转义序列,并忽略任何其他级别的长括号。任何类型的行尾顺序(回车符,换行符,回车符后跟换行符,或换行符后接回车符)都会转换为简单的换行符。
为方便起见,当开头的长括号紧跟着一个换行符时,换行符不包含在字符串中。例如,在使用ASCII的系统中(其中' a
'编码为97,新行编码为10,' 1
'编码为49),下面的五个字符串表示相同的字符串:
a = 'alo\n123"'a = "alo\n123\""a = '\97lo\10\04923"'a = [[alo123"]]a = [==[alo123"]==]
未受前面规则明确影响的文字字符串中的任何字节代表自身。但是,Lua会以文本模式打开文件进行解析,并且系统文件功能可能会对某些控制字符产生问题。因此,将非文本数据表示为带有非文本字符的显式转义序列的引用文本更安全。
一个数字常量(或数字)可以用一个可选的小数部分和一个可选的小数指数来编写,用一个字母' e
'或' E
' 标记。Lua也接受以0x
或开头的十六进制常量0X
。十六进制常量还接受可选的小数部分加上可选的二进制指数,用字母p
“或” 标记P
。带小数点或指数的数字常量表示浮点数; 否则,如果它的值适合整数,它表示一个整数。有效的整数常量的例子是
3 345 0xff 0xBEBADA
Examples of valid float constants are
3.0 3.1416 314.16e-2 0.31416E1 34e10x0.1E 0xA23p-4 0X1.921FB54442D18P+1
一个注释开始用双连字符(--
)的字符串以外的任何地方。如果紧接着的文本--
不是开头的长括号,则注释是一个简短的注释,直到行尾。否则,这是一个很长的评论,一直运行到相应的结束长支架。经常使用长注释来临时禁用代码。
变量是存储值的地方。Lua中有三种变量:全局变量,局部变量和表格字段。
一个名称可以表示一个全局变量或一个局部变量(或一个函数的形式参数,它是一种特殊的局部变量):
var ::= Name
名称表示标识符,如§3.1所定义。
除非明确声明为局部变量,否则假定任何变量名都是全局变量(见§3.3.7)。局部变量在词汇范围内:局部变量可以通过其范围内定义的函数自由访问(请参阅第3.5节)。
在第一次赋值给变量之前,其值为空。
方括号用于索引表:
var ::= prefixexp ‘[’ exp ‘]’
访问表格字段的含义可以通过metatables来更改。对索引变量的访问t[i]
相当于一个调用gettable_event(t,i)
。(关于gettable_event
函数的完整描述,请参阅§2.4,这个函数在Lua中没有定义或调用,我们在这里使用它只是为了说明的目的。)
语法var.Name
只是语法糖var["Name"]
:
var ::= prefixexp ‘.’ Name
对全局变量的访问x
等同于_ENV.x
。由于块被编译的方式,_ENV
永远不会是一个全局名称(见§2.2)。
Lua支持几乎常规的语句集,类似于Pascal或C语言中的语句集。该集包括赋值,控制结构,函数调用和变量声明。
块是一系列语句,按顺序执行:
block ::= {stat}
Lua具有空语句,允许您用分号分隔语句,用分号开始块或按顺序写两个分号:
stat ::= ‘;’
函数调用和赋值可以从左括号开始。这种可能性导致了Lua语法中的模糊性。考虑下面的片段:
a = b + c(print or io.write)('done')
语法可以通过两种方式看到它:
a = b + c(print or io.write)('done')a = b + c; (print or io.write)('done')
当前的解析器总是以第一种方式看到这样的构造,将左括号解释为调用参数的开始。为避免这种歧义,最好总是使用以括号开头的分号语句:
;(print or io.write)('done')
一个块可以被明确地分隔以产生单个语句:
stat ::= do block end
显式块用于控制变量声明的范围。显式块有时也用于在另一个块的中间添加return语句(请参阅第3.3.4节)。
Lua 的编译单位被称为一个块。在语法上,块只是一个块:
chunk ::= block
Lua 将块作为具有可变数量参数的匿名函数的主体来处理 (请参阅第3.4.11节)。因此,块可以定义局部变量,接收参数和返回值。而且,这样的匿名函数被编译为一个外部局部变量的范围_ENV
(参见§2.2)。得到的函数总是具有_ENV
唯一的upvalue,即使它不使用该变量。
块可以存储在主机程序中的文件或字符串中。为了执行块,Lua首先加载它,将块的代码预编译为虚拟机的指令,然后Lua使用虚拟机的解释器执行编译的代码。
块也可以预编译成二进制形式; 详情请参阅程序luac
和功能string.dump
。源代码和编译形式的程序是可以互换的; Lua会自动检测文件类型并相应地执行操作(请参阅load
)。
Lua 允许多个分配。因此,赋值语法定义了左侧的变量列表和右侧的表达式列表。两个列表中的元素用逗号分隔:
stat ::= varlist ‘=’ explist varlist ::= var {‘,’ var}explist ::= exp {‘,’ exp}
表达式在§3.4中讨论。
在赋值之前,将值列表调整为变量列表的长度。如果有更多的值超出需要,超出的值将被丢弃。如果有少于所需的值,该列表将被尽可能多的零的需要。如果表达式列表以一个函数调用结束,那么在调整之前,该调用返回的所有值都会输入值列表(除非调用被括在圆括号中;请参阅第3.4节)。
赋值语句首先评估其所有表达式,然后才执行赋值。这样的代码
i = 3i, a[i] = i+1, 20
设置a[3]
为20,不会影响,a[4]
因为i
在a[i]
分配4之前,对in 进行评估(至3)。同样,该行
x, y = y, x
交换x
和y
,和的值
x, y, z = y, z, x
周期性的置换的值x
,y
和z
。
赋值给全局变量和表字段的含义可以通过metatables来改变。对索引变量的分配t[i] = val
等同于settable_event(t,i,val)
。(关于settable_event
函数的完整描述,请参阅§2.4,这个函数在Lua中没有定义或调用,我们在这里使用它只是为了说明的目的。)
对全局名称x = val
的分配等同于分配_ENV.x = val
(请参阅第2.2节)。
控制结构if,while,and repeat具有通常的含义和熟悉的语法:
stat ::= while exp do block end stat ::= repeat block until exp stat ::= if exp then block {elseif exp then block} [else block] end
Lua 中也有一个用于说法,两种口味(见§3.3.5)。
控件结构的条件表达式可以返回任何值。这两种假和零被认为是假的。所有与nil和false不同的值都被认为是真的(特别是数字0和空字符串也是如此)。
在repeat - until循环中,内部块不会以until关键字结束,而只会在条件之后结束。所以,条件可以引用循环块中声明的局部变量。
将跳转语句将程序控制的标签。出于语法原因,Lua中的标签也被认为是语句:
stat ::= goto Name stat ::= label label ::= ‘::’ Name ‘::’
在定义标签的整个块中都可以看到标签,除了嵌套块中定义了标签名称相同的嵌套块以及嵌套函数内部的标签。只要不进入局部变量的范围,goto可以跳转到任何可见标签。
标签和空语句被称为空语句,因为它们不执行任何操作。
在休息的语句终止执行,同时,重复,或用于循环,跳到循环之后的下一条语句:
stat ::= break
休息结束该最内层循环。
该返回语句用来从一功能或一组块(这是一个匿名功能)返回值。函数可以返回多个值,所以return语句的语法是
stat ::= return [explist] [‘;’]
在返回语句只能写成块的最后陈述。如果真的有必要在块的中间返回,那么可以使用一个明确的内部块,就像在成语中一样do return end
,因为现在return是它的(内部)块中的最后一个语句。
该对语句有两种形式:一个数字和一个通用的。
数值为同时控制变量通过算术级数运行循环重复的代码块。它具有以下语法:
stat ::= for Name ‘=’ exp ‘,’ exp [‘,’ exp] do block end
该块从第一个exp的值开始重复名称,直到它通过第三个exp的步骤传递第二个exp。更确切地说,一个for语句就像
for v = 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 var = var - step while true do var = var + step if (step >= 0 and var > limit) or (step < 0 and var < limit) then break end local v = var block end end
请注意以下几点:
在循环开始之前,所有三个控制表达式都只计算一次。他们都必须得到数字。
var
,limit
并且step
是不可见的变量。此处显示的名称仅用于解释目的。
如果第三个表达式(步骤)不存在,则使用1的步骤。
你可以使用break和goto来退出for循环。
循环变量v
是循环体的局部变量。如果循环后需要它的值,则在退出循环之前将其分配给另一个变量。
通用的声明在工作职能,叫做迭代器。在每次迭代时,调用迭代器函数以产生一个新值,当新值为零时停止。泛型for循环具有以下语法:
stat ::= for namelist in explist do block end namelist ::= Name {‘,’ Name}
一个for语句像
for var_1, ···, var_n in explist do block end
相当于代码:
do local f, s, var = explist while true do local var_1, ···, var_n = f(s, var) if var_1 == nil then break end var = var_1 block end end
请注意以下几点:
explist
只评估一次。其结果是第一个迭代器变量的迭代器函数,状态和初始值。
f
,s
并且var
是不可见的变量。这些名字只是为了解释的目的。
您可以使用break来退出for循环。
循环变量对于循环var_i
是局部的; 之后,你不能用自己的价值观为目的。如果您需要这些值,则在分解或退出循环之前将它们分配给其他变量。
为了允许可能的副作用,函数调用可以作为语句来执行:
stat ::= functioncall
在这种情况下,所有返回的值都会被丢弃。函数调用在§3.4.10中解释。
局部变量可以在块内的任何地方声明。声明可以包括一个初始任务:
stat ::= local namelist [‘=’ explist]
如果存在,初始分配与多重分配具有相同的语义(见§3.3.3)。否则,所有变量都用nil初始化。
一个块也是一个块(见§3.3.2),因此局部变量可以在任何显式块外的块中声明。
局部变量的可见性规则在§3.5中解释。
Lua 中的基本表达式如下:
exp ::= prefixexp exp ::= nil | false | trueexp ::= Numeral exp ::= LiteralString exp ::= functiondef exp ::= tableconstructor exp ::= ‘...’ exp ::= exp binop exp exp ::= unop exp prefixexp ::= var | functioncall | ‘(’ exp ‘)’
数字和文字字符串在§3.1中解释; 变量在§3.2中解释; 函数定义在§3.4.11中解释; 函数调用在§3.4.10中解释; 表的构造函数在§3.4.9中有解释。Vararg表达式由三个点(' ...
')表示,只能在可变参数函数内直接使用; 他们在§3.4.11中有解释。
二进制运算符包括算术运算符(见§3.4.1),位运算符(见§3.4.2),关系运算符(见§3.4.4),逻辑运算符(见§3.4.5)和连接运算符(见§§3.4.5) 3.4.6)。一元运算符包括一元减号(见§3.4.1),一元位运算NOT(见§3.4.2),一元逻辑not(见§3.4.5)和一元运算符(见§3.4.7) 。
函数调用和可变参数表达式可能会导致多个值。如果函数调用被用作语句(参见§3.3.6),那么它的返回列表被调整为零个元素,从而丢弃所有返回的值。如果表达式用作表达式列表的最后一个(或唯一)元素,则不会进行调整(除非表达式用圆括号括起来)。在所有其他情况下,Lua会将结果列表调整为一个元素,要么丢弃除第一个元素之外的所有值,要么在没有值的情况下添加单个零。
这里有些例子:
f() -- adjusted to 0 resultsg(f(), x) -- f() is adjusted to 1 resultg(x, f()) -- g gets x plus all results from f()a,b,c = f(), x -- f() is adjusted to 1 result (c gets nil)a,b = ... -- a gets the first vararg parameter, b gets -- the second (both a and b can get nil if there -- is no corresponding vararg parameter)a,b,c = x, f() -- f() is adjusted to 2 results a,b,c = f() -- f() is adjusted to 3 resultsreturn f() -- returns all results from f()return ... -- returns all received vararg parametersreturn x,y,f() -- returns x, y, and all results from f(){f()} -- creates a list with all results from f(){...} -- creates a list with all vararg parameters{f(), nil} -- f() is adjusted to 1 result
括在括号中的任何表达式总是只会导致一个值。因此,(f(x,y,z))
即使f
返回多个值,也始终是单个值。(如果不返回任何值,则(f(x,y,z))
返回的值是f
或nilf
。)
Lua 支持以下算术运算符:
+
: addition
-
: subtraction
*
: multiplication
/
: float division
//
: floor division
%
: modulo
^
: exponentiation
-
: unary minus
除了幂乘和浮点除法之外,算术运算符的工作方式如下:如果两个操作数都是整数,则操作将在整数上执行,结果为整数。否则,如果两个操作数都是可以转换为数字的数字或字符串(请参阅第3.4.3节),那么它们将转换为浮点数,操作按照通常的浮点算法规则执行(通常是IEEE 754标准) ,结果是浮动。
指数/
运算和浮点除法()总是将它们的操作数转换为浮点数,结果总是浮点数。指数pow
运算使用ISO C函数,因此它也适用于非整数指数。
Floor division(//
)是一个将商朝向负无穷大的部分,即其操作数分区的底部。
模数被定义为将商朝向负无穷(地板分割)四舍五入的分割的其余部分。
在整数算术中溢出的情况下,根据通常的双补数算法规则,所有的操作都会回绕。(换句话说,它们返回等于模264的唯一可表示的整数到数学结果。)
Lua 支持以下按位运算符:
&
: bitwise AND
|
: bitwise OR
~
: bitwise exclusive OR
>>
: right shift
<<
: left shift
~
: unary bitwise NOT
所有按位操作都将其操作数转换为整数(请参阅第3.4.3节),对这些整数的所有位进行操作,并生成一个整数。
右移和左移用零填充空位。负位移转向另一个方向; 绝对值等于或大于整数位数的位移结果为零(所有位都移出)。
Lua 在运行时提供了一些类型和表示之间的自动转换。按位运算符总是将浮点操作数转换为整数。指数运算和浮点除法总是将整数运算符转换为浮点数。应用于混合数字(整数和浮点数)的所有其他算术运算将整数操作数转换为浮点数; 这被称为通常的规则。根据需要,C API也将这两个整数转换为浮点数并浮点到整数。而且,字符串连接除了字符串之外还接受数字作为参数。
只要有数字,Lua 也会将字符串转换为数字。
在从整数到浮点数的转换中,如果整数值具有精确的浮点形式,那就是结果。否则,转换得到最接近的较高或最接近较低的可表示值。这种转换永远不会失败。
从float到integer的转换检查浮点数是否具有精确表示形式(即浮点数是整数值并且处于整数表示范围内)。如果是这样,那就是结果。否则,转换失败。
从字符串到数字的转换过程如下:首先,将字符串转换为整数或浮点数,遵循其语法和Lua词法分析器的规则。(该字符串可能还有前导空格和尾随空格以及符号)。然后,结果数字(浮点数或整数)转换为上下文所需的类型(浮点型或整数型)(例如强制转换的操作)。
从字符串到数字的所有转换都接受点和当前语言环境标记作为基数字符。(然而,Lua词法分析器只接受一个点。)
从数字到字符串的转换使用非指定的可读格式。要完全控制数字如何转换为字符串,请使用format
字符串库中的函数(请参阅参考资料string.format
)。
Lua 支持以下关系运算符:
==
: 平等
~=
:不平等
<
: 少于
>
: 比...更棒
<=
:少或相等
>=
:大于或等于
这些操作员总是导致错误或真实。
Equality(==
)首先比较其操作数的类型。如果类型不同,那么结果是错误的。否则,将比较操作数的值。字符串以明显的方式进行比较。如果数字表示相同的数学值,则数字相等。
表,用户数据和线程按引用进行比较:只有两个对象是相同的对象时,才认为两个对象相等。每次创建一个新对象(一个表,用户数据或线程)时,这个新对象都不同于任何先前存在的对象。具有相同参考的闭包总是相同的。任何可检测到的差异(不同行为,不同定义)的闭包总是不同的。
您可以通过使用“eq”元方法来更改Lua比较表和用户数据的方式(请参阅第2.4节)。
平等比较不会将字符串转换为数字,反之亦然。因此,"0"==0
计算结果为假,并t[0]
和t["0"]
在表中表示不同的条目。
运算符~=
恰恰是否定平等(==
)。
订单操作员的工作如下。如果两个参数都是数字,则根据它们的数学值(不管它们的子类型)进行比较。否则,如果两个参数都是字符串,则根据当前语言环境比较它们的值。否则,Lua会尝试调用“lt”或“le”元方法(请参阅第2.4节)。比较结果a > b
被翻译b < a
并a >= b
翻译成b <= a
。
按照IEEE 754标准,NaN被认为既不小于也不等于也不大于任何值(包括其本身)。
Lua中的逻辑运算符是和,或者,而不是。像控制结构一样(参见§3.3.4),所有逻辑运算符都将false和nil都视为false,其他都视为true。
否定运算符不总是返回false或true。如果该值为false或nil,则该联合运算符并返回其第一个参数; 否则,并返回其第二个参数。如果此值与nil和false不同,则该分离运算符或返回其第一个参数; 否则,或返回其第二个参数。既与和或使用短路评价; 也就是说,第二个操作数仅在必要时才被评估。这里有些例子:
10 or 20 --> 1010 or error() --> 10nil or "a" --> "a"nil and 10 --> nilfalse and error() --> falsefalse and nil --> falsefalse or nil --> nil10 and 20 --> 20
(在本手册中,-->
表示前面表达式的结果。)
Lua 中的字符串连接运算符用两个点(' ..
')表示。如果两个操作数都是字符串或数字,则根据§3.4.3中描述的规则将它们转换为字符串。否则,__concat
metamethod被称为(见§2.4)。
长度运算符由一元前缀运算符表示#
。
字符串的长度是其字节数(即每个字符为一个字节时字符串长度的常用含义)。
应用于表格的长度运算符在该表格中返回边框。表中的边框t
是满足以下条件的任何自然数:
(border == 0 or t[border] ~= nil) and t[border + 1] == nil
换言之,边界是表中任何(自然)索引,其中非零值后面跟着零值(或当索引1为零时为零)。
只有一个边界的表格称为序列。例如,表格{10, 20, 30, 40, 50}
是一个序列,因为它只有一个边界(5)。该表{10, 20, 30, nil, 50}
有两个边界(3和5),因此它不是一个序列。该表{nil, 20, 30, nil, nil, 60, nil}
有三个边界(0,3和6),所以它也不是一个序列。该表{}
是一个边界为0的序列。请注意,非自然键不会影响表是否为序列。
何时t
是序列,#t
返回其唯一边界,这对应于序列长度的直观概念。何时t
不是序列,#t
可以返回其任何边界。(确切的依赖于表的内部表示的细节,这又取决于表的填充方式以及非数字键的内存地址。)
表的长度的计算具有保证的最差时间O(log n),其中n是表中最大的自然键。
程序可以通过元方法修改长度运算符的任何值的行为,但字符串__len
(见§2.4)。
Lua 中的运算符优先级遵循下表,从低优先级到高优先级:
or and< > <= >= ~= ==|~&<< >>..+ -* / // %unary operators (not # - ~)^
像往常一样,您可以使用圆括号来更改表达式的优先顺序。串联(' ..
')和求幂(' ^
')操作符是正确的关联。所有其他二元运算符都是关联的。
表构造函数是创建表的表达式。每次评估构造函数时,都会创建一个新表。构造函数可用于创建空表或创建表并初始化其某些字段。构造函数的一般语法是
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 t = {} t[f(1)] = g t[1] = "x" -- 1st exp t[2] = "y" -- 2nd exp t.x = 1 -- t["x"] = 1 t[3] = f(x) -- 3rd exp t[30] = 23 t[4] = 45 -- 4th exp a = t end
构造函数中赋值的顺序是未定义的。(这个命令只有当有重复的键时才有意义。)
如果列表中的最后一个字段具有形式exp
,而表达式是函数调用或可变参数表达式,则此表达式返回的所有值都会连续进入列表(请参阅第3.4.10节)。
为了方便机器生成的代码,字段列表可以有一个可选的尾随分隔符。
Lua中的函数调用具有以下语法:
functioncall ::= prefixexp args
在函数调用中,第一个prefixexp和args被评估。如果prefixexp的值具有类型函数,则使用给定的参数调用此函数。否则,调用prefixexp“call”metamethod,将prefixexp的值作为第一个参数,然后是原始调用参数(请参阅第2.4节)。
表格
functioncall ::= prefixexp ‘:’ Name args
可以用来调用“方法”。一个调用是语法糖,除了只评估一次。v:name(args)v.name(v,args)v
参数具有以下语法:
args ::= ‘(’ [explist] ‘)’ args ::= tableconstructor args ::= LiteralString
在调用之前评估所有参数表达式。形式的调用是语法糖; 也就是说,参数列表是一个新的表格。形式(或或)的调用是语法糖; 也就是说,参数列表是一个单一的文字字符串。f{fields}f({fields})f'string'f"string"f[[string]]f('string')
表格的呼叫return
functioncall
称为尾部呼叫。Lua实现适当的尾部调用(或适当的尾部递归):在尾部调用中,被调用函数重用调用函数的堆栈条目。因此,程序可以执行的嵌套尾调用的数量没有限制。但是,尾部调用将删除有关调用函数的任何调试信息。请注意,尾部调用仅在特定语法时发生,其中返回有一个函数调用作为参数; 这个语法使得调用函数完全返回被调用函数的返回值。所以,下面的例子都不是尾调用:
return (f(x)) -- results adjusted to 1return 2 * f(x)return x, f(x) -- additional resultsf(x); return -- results discardedreturn x or f(x) -- results adjusted to 1
函数定义的语法是
functiondef ::= function funcbody funcbody ::= ‘(’ [parlist] ‘)’ block end
以下语法糖简化了函数定义:
stat ::= function funcname funcbody stat ::= local function Name funcbody funcname ::= Name {‘.’ Name} [‘:’ Name]
该声明
function f () body end
转化为
f = function () body end
该声明
function t.a.b.c.f () body end
转化为
t.a.b.c.f = function () body end
该声明
local function f () body end
转化为
local f; f = function () body end
不要
local f = function () body end
(当函数的主体包含引用时,这只会有所不同f
。)
函数定义是一个可执行表达式,其值具有类型函数。当Lua预编译一个块时,它的所有函数体也被预编译。然后,每当Lua执行函数定义时,函数就会被实例化(或关闭)。该函数实例(或闭包)是表达式的最终值。
参数作为使用参数值初始化的局部变量:
parlist ::= namelist [‘,’ ‘...’] | ‘...’
当函数被调用时,参数列表被调整为参数列表的长度,除非该函数是一个可变参数函数,...
在参数列表末尾用三个点(' ')表示。变量函数不调整其参数列表; 相反,它收集所有额外的参数并通过可变参数表达式将它们提供给函数,这也写成三个点。此表达式的值是所有实际额外参数的列表,类似于具有多个结果的函数。如果在另一个表达式中或在表达式列表中使用可变表达式,则将其返回列表调整为一个元素。如果表达式用作表达式列表的最后一个元素,则不作调整(除非最后一个表达式括在圆括号中)。
作为例子,请考虑以下定义:
function f(a, b) endfunction g(a, b, ...) endfunction r() return 1,2,3 end
然后,我们有以下从参数到参数和可变参数表达式的映射:
CALL PARAMETERSf(3) a=3, b=nilf(3, 4) a=3, b=4f(3, 4, 5) a=3, b=4f(r(), 10) a=1, b=10f(r()) a=1, b=2g(3) a=3, b=nil, ... --> (nothing)g(3, 4) a=3, b=4, ... --> (nothing)g(3, 4, 5, 8) a=3, b=4, ... --> 5 8g(5, r()) a=5, b=1, ... --> 2 3
结果使用return语句返回(请参阅第3.3.4节)。如果控件在没有遇到return语句的情况下到达函数的结尾,则函数返回而没有结果。
对函数可能返回的值的数量有一个与系统相关的限制。这个限制保证大于1000。
在结肠语法用于定义方法,即,具有一个隐含的额外的参数的功能self
。因此,声明
function t.a.b.c:f (params) body end
是句法糖的
t.a.b.c.f = function (self, params) body end
Lua 是一个词汇范围的语言。局部变量的范围从声明后的第一个语句开始,一直持续到包含声明的最内层块的最后一个非 void 语句。考虑下面的例子:
x = 10 -- global variabledo -- 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) --> 11endprint(x) --> 10 (the global one)
请注意,在一个声明中local x = x
,声明的新x
声明尚未包含在范围内,因此第二个声明是x
外部变量。
由于词汇范围规则,局部变量可以通过其范围内定义的函数自由访问。内部函数使用的局部变量在内部函数内被称为upvalue或外部局部变量。
注意每个本地语句的执行定义了新的局部变量。考虑下面的例子:
a = {}local x = 20for i=1,10 do local y = 0 a[i] = function () y=y+1; return x+y end end
该循环创建十个闭包(即,匿名函数的十个实例)。每个关闭使用一个不同的y
变量,而所有这些关闭共享相同x
。