跳转到内容

Lua 编程/标准库

来自 Wikibooks,为开放世界提供开放书籍

Lua 是一种被称为“没有提供电池”的语言。这意味着它的库被保留在执行某些任务所需的最低限度。Lua 依靠其社区创建可用于执行更特定任务的库。Lua 参考手册 为所有库提供文档[1],因此它们将在本文中简要介绍。除了基本库和包库之外,所有库都将它们的函数和值作为表的字段提供。

基本库

[编辑 | 编辑源代码]

基本库为 Lua 提供核心功能。它的所有函数和值都直接在全局环境中可用,并且默认情况下,所有在全局环境中直接可用的函数和值都是基本库的一部分。

一个断言是一个谓词,开发人员假设它是正确的。它们在程序中使用,以确保特定条件在程序执行的特定时刻为真。断言用于单元测试中验证程序是否正常工作,但也用于程序代码中,在这种情况下,程序将在断言为假时失败,要么验证程序运行的​​环境是否正确,要么验证程序代码中没有错误,并生成适当的错误消息,以便在某些事情未按预期发生时更容易找到代码中的错误。在 Lua 中,断言使用assert函数进行,该函数接受一个条件和一条消息(默认为“断言失败!”)作为参数。当条件计算为假时,assert会抛出一个带有消息的错误。当它计算为真时,assert会返回所有参数。

垃圾收集

[编辑 | 编辑源代码]

垃圾收集是由 Lua 和许多其他语言实现的一种自动内存管理形式。当程序需要在变量中存储数据时,它会要求操作系统在计算机内存中分配空间来存储变量的值。然后,当它不再需要该空间时(通常是因为变量超出了范围),它会告诉操作系统释放该空间,以便其他程序可以使用它。在 Lua 中,实际过程要复杂得多,但基本思想是一样的:程序必须告诉操作系统它何时不再需要变量的值。在低级语言中,分配由语言处理,但释放没有,因为语言无法知道程序员何时不再需要值:即使引用该值的变量超出了范围或被删除,另一个变量或脚本中的一个字段可能仍然引用它,而释放它会导致问题。在高级语言中,释放可能由各种自动内存管理系统处理,例如垃圾收集,这是 Lua 使用的系统。垃圾收集器定期搜索 Lua 分配的所有值,以查找任何地方都没有引用的值。它将收集程序不再可以访问的值(因为没有对它们的引用),并且因为它知道这些值可以安全地释放,所以会释放它们。所有这些都是透明且自动完成的,因此程序员通常不需要为此做任何事情。但是,有时,开发人员可能希望对垃圾收集器发出指示。

弱引用

[编辑 | 编辑源代码]

弱引用是指垃圾收集器会忽略的引用。这些引用由开发人员使用mode元方法指示给垃圾收集器。表的mode元方法应该是一个字符串。如果该字符串包含字母“k”,则该表所有字段的键都是弱的,如果包含字母“v”,则该表所有字段的值都是弱的。当一个对象数组具有弱值时,即使这些对象在该数组中被引用,垃圾收集器也会收集它们,只要它们只在该数组和其他弱引用中被引用即可。这种行为有时很有用,但很少使用。

操纵垃圾收集器

[编辑 | 编辑源代码]

可以使用collectgarbage函数操纵垃圾收集器,该函数是基本库的一部分,充当垃圾收集器的接口。它的第一个参数是一个字符串,它指示垃圾收集器应该执行什么操作;第二个参数由某些操作使用。collectgarbage函数可用于停止垃圾收集器、手动执行收集周期以及计算 Lua 使用的内存。

协程计算机程序组件,它们概括了子例程,以允许在某些位置暂停和恢复执行的多个入口点。协程非常适合实现更熟悉的程序组件,例如协作式多任务异常处理事件循环迭代器无限列表管道
—维基百科, 协程

协程是可以在 Lua 中使用协程库创建和操纵的组件,这些组件允许通过从自身内部调用会产生协程的函数或从外部调用恢复协程的函数,在特定位置产生和恢复函数的执行。示例

  1. 主线程中的一个函数使用coroutine.create从一个函数创建协程,并使用coroutine.resume恢复它,并将数字 3 传递给它。
  2. 协程中的函数执行并获得作为coroutine.resume参数传递的数字。
  3. 该函数在执行过程中的某个点调用coroutine.yield,并将它收到的参数(3)和 2 的总和(因此 3+2=5)作为参数传递。
  4. coroutine.resume的调用返回 5,因为它被传递给了coroutine.yield,并且现在再次运行的主线程将该数字存储在一个变量中。它在执行了一些代码后再次恢复协程,并将它从coroutine.resume调用中收到的值的双倍传递给coroutine.resume(即它传递 5×2=10)。
  5. 协程获得作为coroutine.yield调用结果传递给coroutine.resume的值,并在运行更多代码后终止。它返回coroutine.yield调用结果和最初作为参数给它的值之间的差(即 10−3=7)。
  6. 主线程获取作为coroutine.resume调用结果由协程返回的值,并继续执行。

此示例在代码中给出以下结果

local co = coroutine.create(function(initial_value)
	local value_obtained = coroutine.yield(initial_value + 2) -- 3+2=5
	return value_obtained - initial_value -- 10-3=7
end)

local _, initial_result = coroutine.resume(co, 3) -- initial_result: 5
local _, final_result = coroutine.resume(co, initial_result * 2) -- 5*2=10
print(final_result) --> 7

coroutine.create 函数使用函数创建一个协程;协程是“线程”类型的值。coroutine.resume 启动或继续协程的执行。当协程遇到错误或没有剩余可运行内容时,协程就被认为是死亡的(在这种情况下它会终止执行)。当协程死亡时,它无法恢复。如果协程的执行成功,coroutine.resume 函数将返回 true,以及所有返回的值(如果协程已终止)或传递给 coroutine.yield 的值(如果它尚未终止)。如果执行不成功,它将返回 false 以及错误消息。coroutine.resume 返回正在运行的协程和 true(当该协程为主线程时),否则返回 false

coroutine.status 函数以字符串形式返回协程的状态。

  • 如果协程正在运行,则为“running”,这意味着它必须是调用 coroutine.status 的协程。
  • 如果协程在调用 yield 时被挂起,或者它尚未开始运行,则为“suspended”。
  • 如果协程处于活动状态但未运行,则为“normal”,这意味着它已恢复另一个协程。
  • 如果协程已完成运行或遇到错误,则为“dead”。

coroutine.wrap 函数返回一个函数,每次调用该函数都会恢复协程。传递给此函数的额外参数将充当传递给 coroutine.resume 的额外参数,并且由协程返回或传递给 coroutine.yield 的值将由对该函数的调用返回。与 coroutine.resume 不同,coroutine.wrap 函数不会在受保护模式下调用协程,并且会传播错误。

协程有很多用例,但描述它们超出了本书的范围。

字符串匹配

[编辑 | 编辑源代码]

在操作字符串时,能够在字符串中搜索符合特定模式的子字符串通常很有用。Lua 拥有一个字符串操作库,它提供用于执行此操作的函数,以及用于表达这些函数可以在字符串中搜索的模式的符号。Lua 提供的符号与正则表达式非常相似,这是一种用于表达编程世界中大多数语言和工具使用的模式的符号。但是,它更有限,并且具有略微不同的语法。

字符串库的 find 函数在字符串中查找模式的第一个匹配项。如果它在字符串中找到了模式的出现,它将返回字符串中的索引(表示字符串中字符位置的整数,从第一个字符开始,位于位置 1),其中出现开始和结束。如果它没有找到模式的出现,它将不返回任何内容。它接受的第一个参数是字符串,第二个参数是模式,第三个参数是表示 find 函数应该开始搜索的字符位置的整数。最后,find 函数可以被告知通过传递 true 作为其第四个参数来执行简单的匹配,而无需使用模式。然后,它将只在第一个字符串中搜索给定的第二个字符串的出现。当执行简单匹配时,必须给出第三个参数。此示例代码在句子中搜索单词“lazy”,并打印找到的单词的开始和结束位置。

local start_position, end_position = string.find("The quick brown fox jumps over the lazy dog.", "lazy", 1, true)
print("The word \"lazy\" was found starting at position " .. start_position .. " and ending at position " .. end_position .. ".")

此代码给出的结果为The word "lazy" was found starting at position 36 and ending at position 39.。它等效于以下内容

local sentence = "The quick brown fox jumps over the lazy dog."
local start_position, end_position = sentence:find("lazy", 1, true)
print("The word \"lazy\" was found starting at position " .. start_position .. " and ending at position " .. end_position .. ".")

这是可行的,因为字符串的 index 元方法被设置为包含字符串库函数的表,使得能够用 b:a(...) 替换 string.a(b, ...)

字符串库中接受索引以指示字符位置或返回此类索引的函数将第一个字符视为位于位置 1。它们接受负数并将它们解释为从字符串末尾向后索引,最后一个字符位于位置 -1。

模式是遵循特定符号的字符串,以指示字符串可能匹配或不匹配的模式。为此,模式包含字符类,它们代表字符集的组合。

字符组合 描述
. 所有字符
%a 字母(大写和小写)
%c 控制字符
%d 数字
%g 可打印字符(空格字符除外)
%l 小写字母
%p 标点符号字符
%s 空格字符
%u 大写字母
%w 字母数字字符(数字和字母)
%x 十六进制数字

所有不是特殊字符的字符都代表它们自己,特殊字符(所有不是字母数字的字符)可以通过在它们前面加上百分号来转义。字符类可以通过放在集合中来组合以创建更大的字符类。集合被标记为在方括号之间标记的字符类(即 [%xp] 是所有十六进制字符加上字母“p”的集合)。字符范围可以通过用连字符(例如 [0-9%s] 代表从 0 到 9 的所有数字加上空格字符)分隔范围的结束字符(按升序排列)来表示。如果脱字符 (“^”) 字符放在集合的开头(紧接在左方括号之后),则集合将包含所有字符,除了它在该脱字符未放在集合的开头时会包含的字符。

所有由带百分号的字母表示的类的补码可以表示为一个百分号后跟相应的字母(例如 %S 表示除空格字符以外的所有字符)。

模式是模式项的序列,这些序列代表了字符串应该匹配哪些序列才能与它匹配。模式项可以是字符类,在这种情况下,它只匹配类中的任何一个字符一次;字符类后跟 “*” 字符,在这种情况下,它匹配类中 0 个或多个字符的重复(这些重复项将始终匹配最长的可能序列);字符类后跟加号 (“+”) 字符,在这种情况下,它匹配类中 1 个或多个字符的重复(这些重复项也将始终匹配最长的可能序列);字符类后跟减号 (“-”) 字符,在这种情况下,它匹配类中 0 个或多个字符的重复,但匹配最短的可能序列;或者字符类后跟问号,在这种情况下,它匹配类中一个或没有一个字符的出现。

可以匹配等效于先前捕获的子字符串的子字符串:%1 将匹配第一个捕获的子字符串,%2 匹配第二个子字符串,依此类推,直到 %9。捕获在下面描述。模式提供了另外两种功能,如参考手册所述

%bxy,其中 xy 是两个不同的字符;这样的项目匹配以 x 开头、以 y 结尾的字符串,并且 xy 是平衡的。这意味着,如果从左到右读取字符串,对 x 计数 +1,对 y 计数 -1,则结束的 y 是计数达到 0 的第一个 y。例如,项目 %b() 匹配具有平衡括号的表达式。

%f[set],一个边界模式;这样的项目在任何位置匹配一个空字符串,使得下一个字符属于集合,而前一个字符不属于集合。集合 set 的解释如前所述。主体开头和结尾的处理方式就像它们是字符 '\0' 一样。

—Lua 作者, Lua 5.2 参考手册

模式是模式项的序列,前面可以选择加一个脱字符,表示模式只能匹配字符串的开头,后面可以选择加一个美元符号,表示模式只能匹配字符串的结尾。据说这些符号锚定字符串开头或结尾的匹配。这两个字符只有在模式开头或结尾时才具有特殊含义。

子模式可以包含在模式内的括号内,以指示捕获。当匹配成功时,与捕获匹配的字符串的子字符串将被存储以供将来使用,例如,被 gmatch 返回。它们总是从其左括号的位置开始编号。两个空括号表示空捕获,它捕获当前字符串位置(它是一个数字,不是字符串的一部分)。

gmatch 函数可以用于迭代字符串中模式的出现;与 find 函数不同,不可能指定开始搜索的初始位置或执行简单匹配。gmatch 函数返回一个迭代器,当调用时,它会返回字符串中给定模式的下一个捕获。如果没有在模式中指定捕获,则会给出整个匹配。以下示例显示了如何迭代句子中的单词,并逐个打印它们

local sentence = "The quick brown fox jumps over the lazy dog."
for word in sentence:gmatch('%a+') do
	print(word)
end

在此示例中,整个匹配由迭代器返回的唯一值 word 给出。

gsub 函数可用于将字符串中模式的所有出现替换为其他内容。它的前两个参数是字符串和模式,而第三个参数是要替换出现的字符串,第四个参数是要替换出现的最大次数。第三个参数可以是表或函数,而不是字符串。

当第三个参数是字符串时,它被称为替换字符串,它将字符串中匹配模式的出现次数替换掉。由模式存储的捕获可以嵌入到替换字符串中;它们用百分号后跟代表捕获编号的数字来表示。匹配本身可以用%0表示。替换字符串中的百分号必须转义为%%

当第三个参数是表格时,第一个捕获被用作索引该表格的键,而替换字符串是表格中对应于该键的值。当它是一个函数时,该函数会在每次匹配时被调用,所有捕获都作为参数传递。在这两种情况下,如果没有捕获,则使用整个匹配。如果函数或表格返回值falsenil,则不会进行任何替换。

以下是一些直接来自 Lua 5.2 参考手册的示例

x = string.gsub("hello world", "(%w+)", "%1 %1")
--> x="hello hello world world"

x = string.gsub("hello world", "%w+", "%0 %0", 1)
--> x="hello hello world"

x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")
--> x="world hello Lua from"

x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv)
--> x="home = /home/roberto, user = roberto"

x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s)
      return load(s)()
    end)
--> x="4+5 = 9"

local t = {name="lua", version="5.2"}
x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
--> x="lua-5.2.tar.gz"
—Lua 作者, Lua 5.2 参考手册

Lua 提供了除模式匹配之外的其他用于操作字符串的函数。这些函数包括reverse函数,它返回一个字符顺序反转的字符串,lower函数,它返回一个字符串的小写等效项,upper函数,它返回一个字符串的大写等效项,len函数,它返回一个字符串的长度,以及sub函数,它返回一个字符串的子字符串,该子字符串从给定的两个字符位置作为参数开始并结束。还有更多,它们在参考手册中都有记录。

  1. Ierusalimschy, Roberto; Celes, Waldemar; Henrique de Figueiredo, Luiz. Lua 5.2 参考手册. 检索于 2013 年 11 月 30 日.
华夏公益教科书