Lua 编程/语句
语句 是可以执行的代码片段,包含指令和用于该指令的表达式。一些语句还包含自身内部的代码,例如,可能在特定条件下运行。与表达式不同,它们可以直接放入代码中,并将执行。Lua 只有少数指令,但这些指令结合其他指令和复杂的表达式,为用户提供了大量的控制和灵活性。
程序员经常需要能够将值存储在内存中以便稍后使用。这是使用变量完成的。 变量 是对存储在计算机内存中的值的引用。它们可以用来在将数字存储在内存中之后访问它。 赋值 是用于将值赋给变量的指令。它包括要存储值的变量的名称、一个等号以及要存储在变量中的值。
variable = 43
print(variable) --> 43
如上面代码所示,可以通过将变量的名称放在需要访问值的地方来访问变量的值。
在 Lua 中,与大多数其他编程语言一样,等号 (=
) 充当二元 赋值运算符,将右侧操作数表达式的值赋给左侧操作数命名的变量。
以下示例展示了使用等号进行变量赋值。
fruit = "apple" -- assign a string to a variable
count = 5 -- assign a numeric value to a variable
请注意,文字字符串应包含在引号中,以将其与变量名区分开来。
apples = 5
favourite = "apples" -- without quotes, apples would be interpreted as a variable name
请注意,数值不需要包含在引号中,也不会被误解为变量名,因为变量名不能以数字开头。
apples = 6 -- no quotes are necessary around a numeric parameter
pears = "5" -- quotes will cause the value to be considered a string
Lua 编程语言支持多重赋值。
apples, favorite = 5, "apples" -- assigns apples = 5, favorite = "apples"
标识符,在 Lua 中也称为名称。它们可以是任何由字母、数字和下划线组成的文本,并且不能以数字开头。它们用于命名变量和表字段,这些将在关于表的章节中介绍。
以下是一些有效的名称
name
hello
_
_tomatoes
me41
__
_thisIs_StillaValid23name
以下是一些无效的名称
2hello
: 以数字开头th$i
: 包含不是字母、数字或下划线的字符hel!o
: 包含不是字母、数字或下划线的字符563text
: 以数字开头82_something
: 以数字开头
此外,以下关键字由 Lua 保留,不能用作名称:and
, break
, do
, else
, elseif
, end
, false
, for
, function
, if
, in
, local
, nil
, not
, or
, repeat
, return
, then
, true
, until
, while
。
命名变量或表字段时,必须为其选择一个有效的名称。因此,它必须以字母或下划线开头,并且只能包含字母、下划线和数字。请注意,Lua 区分大小写。这意味着 Hello
和 hello
是两个不同的名称。
变量的作用域 是程序代码中该变量有意义的区域。您之前见过的变量示例都是全局变量的示例,这些变量可以从程序中的任何地方访问。另一方面,局部变量只能从定义它们的程序区域以及位于该程序区域内部的程序区域使用。它们与全局变量的创建方式完全相同,但必须以 local
关键字为前缀。
do
语句将用于描述它们。do
语句是一个除了创建新的代码块(因此新的作用域)之外没有其他用途的语句。它以 end
关键字结尾。
local variable = 13 -- This defines a local variable that can be accessed from anywhere in the script since it was defined in the main region.
do
-- This statement creates a new block and also a new scope.
variable = variable + 5 -- This adds 5 to the variable, which now equals 18.
local variable = 17 -- This creates a variable with the same name as the previous variable, but this one is local to the scope created by the do statement.
variable = variable - 1 -- This subtracts 1 from the local variable, which now equals 16.
print(variable) --> 16
end
print(variable) --> 18
当作用域结束时,其中的所有变量都将被清除。代码区域可以使用它们包含的代码区域中定义的变量,但如果它们通过定义具有相同名称的局部变量来“覆盖”它们,则将使用该局部变量而不是在其他代码区域中定义的变量。这就是为什么对 print 函数的第一次调用打印 16,而第二次调用(位于由 do
语句创建的作用域之外)打印 18 的原因。
在实践中,应该只使用局部变量,因为它们可以比全局变量更快地定义和访问,因为它们存储在寄存器中,而不是像全局变量一样存储在当前函数的环境中。寄存器是 Lua 用于快速访问局部变量的区域,通常最多只能包含 200 个局部变量。处理器(所有计算机的一个重要组成部分)也有寄存器,但这些与 Lua 的寄存器无关。每个函数(包括主线程,程序的核心,也是一个函数)也有自己的环境,它是一个表,使用索引作为变量名,并将这些变量的值存储在与这些索引相对应的值中。
增量赋值,也称为复合赋值,是一种赋值类型,它为变量赋予一个相对于其先前值的赋值,例如,增加当前值。代码a += 8
的等效代码,它将a的值增加8,已知存在于C、JavaScript、Ruby、Python中,但在Lua中不存在,这意味着需要编写a = a + 8
。
链式赋值是一种赋值类型,它为多个变量赋予单个值。例如,代码a = b = c = d = 0
将在C和Python中将a、b、c和d的值设置为0。在Lua中,此代码将引发错误,因此需要像这样编写前面的示例
d = 0
c = d -- or c = 0
b = c -- or b = 0
a = b -- or a = 0
并行赋值,也称为同时赋值和多重赋值,是一种赋值类型,它同时将不同的值(它们也可以是相同的值)分配给不同的变量。与链式赋值和增量赋值不同,并行赋值在Lua中可用。
上一节中的示例可以改写为使用并行赋值
a, b, c, d = 0, 0, 0, 0
如果提供的变量比值多,则某些变量将不会被分配任何值。如果提供的值比变量多,则多余的值将被忽略。更准确地说,在赋值发生之前,值的列表会根据变量列表的长度进行调整,这意味着多余的值会被移除,并且在列表末尾添加额外的nil值以使其与变量列表具有相同的长度。如果函数调用出现在值列表的末尾,则它返回的值将添加到该列表的末尾,除非函数调用放在括号中。
此外,与大多数编程语言不同,Lua通过置换使变量值的重新赋值成为可能。例如
first_variable, second_variable = 54, 87
first_variable, second_variable = second_variable, first_variable
print(first_variable, second_variable) --> 87 54
这是因为赋值语句在分配任何值之前评估所有变量和值。赋值执行就好像它们是真正同时进行的,这意味着你可以在同一时间将值分配给变量和用该变量的值作为索引的表字段,然后再为它分配新值。换句话说,下面的代码将dictionary[2]
设置为12,而不是dictionary[1]
。
dictionary = {}
index = 2
index, dictionary[index] = index - 1, 12
条件语句是检查表达式是否为真并执行特定代码块的指令。如果表达式不为真,则它们只是跳过该代码块,程序继续执行。在Lua中,唯一的条件语句使用if
指令。False和nil都被视为false,而其他所有内容都被视为true。
local number = 6
if number < 10 then
print("The number " .. number .. " is smaller than ten.")
end
-- Other code can be here and it will execute regardless of whether the code in the conditional statement executed.
在上面的代码中,变量number通过赋值语句被分配了数字6。然后,一个条件语句检查变量number中存储的值是否小于十,这里的情况就是这样。如果是,它将打印"数字6小于十。"。
也可以使用else
关键字仅当表达式不为真时执行特定代码块,并使用elseif
关键字链接条件语句
local number = 15
if number < 10 then
print("The number is smaller than ten.")
elseif number < 100 then
print("The number is bigger than or equal to ten, but smaller than one hundred.")
elseif number ~= 1000 and number < 3000 then
print("The number is bigger than or equal to one hundred, smaller than three thousands and is not exactly one thousand.")
else
print("The number is either 1000 or bigger than 2999.")
end
请注意,else
块必须始终是最后一个。else
块之后不能有elseif
块。仅当在它们之前的所有块都没有执行时,elseif
块才有意义。
用于比较两个值的运算符(其中一些在上面的代码中使用)称为关系运算符。如果关系为真,则它们返回布尔值true
。否则,它们返回布尔值false
。
等于 | 不等于 | 大于 | 小于 | 大于或等于 | 小于或等于 | |
---|---|---|---|---|---|---|
数学符号 | = | ≠ | > | < | ≥ | ≤ |
Lua运算符 | == | ~= | > | < | >= | <= |
上面的代码还演示了如何使用and
关键字在条件表达式中组合多个布尔表达式。
程序员经常需要多次运行特定代码块或类似的代码块,或者根据用户输入运行特定代码块一定次数。循环是指定一次但可以连续执行多次的语句序列。
条件控制循环是由条件控制的循环。它们与条件语句非常相似,但不是在条件为真时执行代码,否则跳过它,而是会不断运行它,直到条件为真,或者直到条件为假。Lua有两种用于条件控制循环的语句:while
循环和repeat
循环。此类循环将运行代码,然后检查条件是否为真。如果为真,则再次运行代码,并重复执行,直到条件为假。当条件为假时,它们停止重复代码,程序流程继续。每次代码执行称为一次迭代。while
和repeat
循环之间的区别在于,repeat
循环将在循环结束时检查条件,而while
循环将在循环开始时检查条件。这只会对第一次迭代产生影响:repeat
循环将始终至少执行一次代码,即使条件在第一次执行代码时为假。while
循环的情况并非如此,只有在条件实际为真时,它们才会在第一次执行代码。
local number = 0
while number < 10 do
print(number)
number = number + 1 -- Increase the value of the number by one.
end
上面的代码将打印0,然后打印1,然后打印2,然后打印3,依此类推,直到9。在第十次迭代之后,number将不再小于十,因此循环将停止执行。有时,循环意味着永远运行,在这种情况下,它们被称为无限循环。例如,渲染器(在屏幕上绘制事物的软件进程)通常会不断循环以重新绘制屏幕,以更新显示给用户的图像。在电子游戏中,这种情况经常发生,因为游戏视图必须不断更新,以确保用户看到的内容保持最新。但是,循环需要永远运行的情况很少,并且此类循环通常是错误的结果。无限循环会消耗大量计算机资源,因此务必确保循环始终结束,即使收到来自用户的意外输入。
local number = 0
repeat
print(number)
number = number + 1
until number >= 10
上面的代码将与使用while
循环的代码执行完全相同的事情。主要区别在于,与while
循环不同,while
循环将条件放在while
关键字和do
关键字之间,而repeat
循环将条件放在循环的末尾,在until
关键字之后。repeat
循环是Lua中唯一创建代码块且不以end
关键字结尾的语句。
增加变量是将它的值增加步长,特别是增加一个步长。上一节中的两个循环增加了变量number并用它来运行代码一定次数。这种循环非常常见,以至于大多数语言(包括Lua)都为此构建了内置控制结构。这种控制结构称为计数控制循环,在Lua和大多数语言中,它由for
语句定义。在这些循环中使用的变量称为循环计数器。
for number = 0, 9, 1 do
print(number)
end
上面的代码与上一节中介绍的两个循环做完全相同的事情,但 number 变量只能在循环内部访问,因为它对循环是局部的。变量名和等号后面的第一个数字是初始化值。它是循环计数器的初始值。第二个数字是循环停止时的数字。它将递增变量并重复代码,直到变量达到该数字。最后,第三个数字是增量:它是每次迭代时循环计数器增加的值。如果未给出增量,Lua 将假设为 1。因此,下面的代码将打印 1、1.1、1.2、1.3、1.4 和 1.5。
for n = 1, 2, 0.1 do
print(n)
if n >= 1.5 then
break -- Terminate the loop instantly and do not repeat.
end
end
上面的代码没有一直到 2,而只到 1.5 的原因是 break
语句,它立即终止循环。此语句可用于任何循环,包括 while
循环和 repeat
循环。请注意,此处使用了 >=
运算符,尽管理论上 ==
运算符也可以完成工作。这是由于小数精度误差。Lua 使用 双精度浮点格式 表示数字,该格式将数字存储在内存中,作为其实际值的近似值。在某些情况下,近似值将完全匹配数字,但在某些情况下,它将只是一个近似值。通常,这些近似值将足够接近数字,不会造成影响,但此系统在使用等号运算符时会导致错误。这就是为什么在处理小数时,通常更安全地避免使用等号运算符。在本例中,如果使用了等号运算符,代码将无法工作[1](它将继续上升到 1.9),但它适用于 >=
运算符。
代码块
[edit | edit source]代码块是一系列按顺序执行的语句。这些语句可以包含空语句,不包含任何指令。空语句可以用来用分号开始一个代码块,或者在序列中写两个分号。
函数调用和赋值可以以括号开头,这会导致歧义。这个片段就是一个例子
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')
代码块
[edit | edit source]Lua 的编译单元称为 代码块。代码块可以存储在文件中,也可以存储在宿主程序中的字符串中。要执行代码块,Lua 首先将代码块预编译成虚拟机的指令,然后使用虚拟机的解释器执行编译后的代码。代码块也可以使用 luac
(随 Lua 附带的编译程序)或 string.dump
函数(返回包含其给定函数的二进制表示的字符串)预编译成二进制形式(字节码)。
load
函数可以用来加载代码块。如果给 load
函数的第一个参数是一个字符串,那么代码块就是该字符串。在这种情况下,该字符串可以是 Lua 代码或 Lua 字节码。如果第一个参数是一个函数,load
将重复调用该函数来获取代码块的各个部分,每个部分都是一个将与前面的字符串连接在一起的字符串。然后,当没有返回任何内容或返回空字符串时,就会认为代码块已完成。
如果没有任何语法错误,load
函数将返回编译后的代码块作为函数。否则,它将返回 nil 和错误消息。
load
函数的第二个参数用于设置代码块的源代码。所有代码块都保留其源代码的副本,以便能够提供合适的错误消息和调试信息。默认情况下,该源代码副本将是传递给 load
的代码(如果给出了代码;如果给出了函数,它将是 "=(load)")。可以使用此参数来更改它。这在编译代码以防止人们获取原始源代码时特别有用。然后,需要删除包含在二进制表示中的源代码,因为否则可以从中获取原始代码。
load
函数的第三个参数可以用来设置生成的函数的环境,第四个参数控制代码块可以是文本还是二进制。它可以是字符串 "b"(仅二进制代码块)、"t"(仅文本代码块)或 "bt"(二进制和文本代码块)。默认值为 "bt"。
还有一个 loadfile
函数,它的工作原理与 load
完全相同,只是它从文件中获取代码。第一个参数是从中获取代码的文件名。没有参数可以修改存储在二进制表示中的源代码,load
函数的第三个和第四个参数对应于此函数的第二个和第三个参数。loadfile
函数也可以用来从标准输入加载代码,如果未给出文件名,则会执行此操作。
dofile
函数类似于 loadfile
函数,但它不是将文件中的代码加载为函数,而是立即将源代码文件中包含的代码作为 Lua 代码块执行。它的唯一参数用于指定要执行其内容的文件名;如果没有给出参数,它将执行标准输入的内容。如果代码块返回值,它们将由对 dofile
函数的调用提供。由于 dofile
不在受保护模式下运行,因此通过它执行的代码块中的所有错误都会传播。