停用的 Lua 常见问题解答 |
|
无需声明:Lua 既没有也不需要变量声明。
但为什么要声明变量?
有些人希望声明变量以防止输入错误。例如,代码
color="red" print(colour)
将打印 nil,因为第 2 行中的 color 被拼写错误,而 colour 未定义。
为了防止这些错误,并发现未定义的变量,您可以使用 getglobal 标签方法
function safe_getglobal(x) local v=rawgetglobal(x) if v then return v else error("undefined global variable "..x) end end settagmethod(tag(nil),"getglobal",safe_getglobal)
有了这段代码,任何试图使用未定义变量(其值为 nil)的尝试都将产生错误消息。
Lua 是一种动态类型语言。这意味着变量没有类型,只有值才有类型。由于 Lua 提供了对值类型的访问(通过 type 函数),您可以编写自己的动态类型函数。以下是一种可能的方案。
要检查简单类型,只需使用 type。有趣的情况是检查表,因为我们必须检查所有字段是否存在并正确填充,符合预定义的“模板”。因此,我们需要一种方法来描述表的“类型”。如上所述,简单类型由其名称描述,由 type 函数报告。表由“类型模板”描述,这些模板是映射每个字段到其所需类型的表。以下是一些对用户界面工具包有用的示例
TNumber="number" TPoint={x=TNumber, y=TNumber} TColor={red=TNumber, blue=TNumber, green=TNumber} TRectangle={topleft=TPoint, botright=TPoint} TWindow={title="string", bounds=TRectangle, color=TColor}
给定这些描述,以下函数检查一个值是否具有给定的类型
function checkType(d, t) if type(t) == "string" then -- t is the name of a type return (type(d) == t) else -- t is a table, so d must also be a table if type(d) ~= "table" then return nil else -- d is also a table; check its fields local i,v = next(t,nil) while i do if not checkType(d[i],v) then return nil end i,v = next(t,i) end end end return 1 end
在大型项目以及库中,有时重要的是保护某些变量,使其不能被重新定义。当加载了多个库并且您想确保它们不会意外地重新定义彼此的函数时,这一点尤其有用。
您可以通过使用 setglobal 标签方法来保护 Lua 中的函数
function protect(x) error("cannot redefine "..x) end settagmethod(tag(protect),"setglobal",protect)
这段代码应该在所有库加载后运行。
有了这段代码,任何试图重新定义函数的尝试都将产生错误消息。您仍然可以重新定义函数,但必须显式地进行
rawsetglobal("print",nil) function print (x) ... end
要将此方案扩展到其他类型的值,您需要用表包装值,并同时使用 setglobal 和 getglobal 标签方法
RO=newtag() -- tag for read-only values function ROvalue(value) -- make a read-only value local t={value=value} settag(t,RO) return t end function ROsetglobal(x) -- protects assignment error(x.." is read-only") end function ROgetglobal(x,value) -- get the actual value return value.value end settagmethod(RO,"getglobal",ROgetglobal) settagmethod(RO,"setglobal",ROsetglobal) tolerance=ROvalue(0.12) -- make some read-only variables color=ROvalue("red") myprint=ROvalue(print)
此方案仅影响使用 ROvalue 创建的变量,因为只有那些带有 RO 标签。
Lua 5.0 现在有了块注释语法
--Double-dashes begin a single-line comment --[[Double-dashes immediately followed by double left square bracket Begin a multi-line comment That continues until the __matching__ double right square bracket, so you can do [[this]] too. ]]
Lua 没有用于块注释的特殊语法。一种解决方案是将注释括在字符串中
comment = [[
this is my
multi-line comment
]]
此解决方案的一个变体,方便暂时禁用一段代码,如下所示
comment = [[
disabled code
--]]
您可以通过向赋值添加 -- 来启用代码
--comment = [[ enabled code --]]
如果块很大,您可能想避免让它一直占用内存直到垃圾收集器将其删除。您可以这样做
local comment = [[ disabled code --]] ; comment = nil
Luiz Henrique de Figueiredo 也建议
do local C=[[ --]] end
“C 立即超出范围。解析器可以利用这一点(但它没有)。还有:”
local C= 1 or [[ <syntaxly correct code to comment out> --]]
“代码生成器可以利用这一点(但它没有)。”
另一种解决方案是使用编辑器宏来前缀选定的行,例如使用“-- ”;或“--~ ”,这样注释块就能在周围的常规注释中脱颖而出。
要注释掉一个代码块,可以使用 if nil then ... end。(请参阅下一个问题。)
在 Lua 中,表是通过引用来操作的。因此,如果 x 的值是一个表,那么赋值 y=x 不会复制该表:x 和 y 包含同一个表。(换句话说,x 和 y 的值是对同一个表的引用。)
如果您确实想复制一个表,可以使用内置函数 next,如下面的代码所示
function clone(t) -- return a copy of the table t local new = {} -- create a new table local i, v = next(t, nil) -- i is an index of t, v = t[i] while i do new[i] = v i, v = next(t, i) -- get next index end return new end
如果您想要深度复制,请在 new[i] = v 之前添加 if type(v)=="table" then v=clone(v) end。
在 Lua 中实现集合的最佳方法是将元素存储为表中的键。当表中对应的值为 nil 时,表示元素存在于集合中。
这里有一些集合的代码片段
s={} -- create an empty set
s[x]=1 -- insert element x into set s
s[x]=nil -- remove element x from set s
x_is_present=s[x] -- does s contain x?
多重集(Bags)与集合类似,除了元素可以出现多次。多重集可以类似集合来实现,但使用与元素关联的值作为计数器。这是一个多重集的代码片段
-- insert element x into bag s if s[x] then s[x] = s[x]+1 else s[x] = 1 end -- remove element x from set s if s[x] then s[x] = s[x]-1 if s[x] == 0 then s[x] = nil end end
从 Lua 5.0 开始,使用 string.char(n) 或甚至 string.format("%c", n)。在 Lua 5.0 之前,使用 strchar(n) 或 format("%c",n)。
[需要更新!]
read 的模式匹配功能很强大,但编写模式并非易事。这里有一些有用的模式
Word (sequence of letters and digits)
{{"%w%w*"}}
Word (sequence of non-white space characters)
{{"%S%S*"}}
Integer
{{"[+-]?%d%d*"}}
Real
{{"[+-]?%d%d*[.]%d%d*"}}
Double quoted string
{{'"[^"]*"'}}
Single quoted string
{{"'[^']*'"}}
如果您想在读取项之前跳过空格,只需在模式前加上 "{%s*}"。例如,"{%s*}%S%S*" 会跳过空格,然后读取下一个单词。
3.2 版本使这更加简单:您可以使用 "*w" 读取一个单词,使用 "*n" 读取一个数字。有关预定义模式的完整列表,请参见参考手册。
用任何语言编写一个自我复制程序都很有趣,但并不总是容易,因为您必须小心引号。在 Lua 中很容易,因为有备选的引号
y = [[ print("y = [[" .. y .. "]]\ndostring(y)") ]]
dostring(y)
版本通知:以上是 Lua 4.0。
有关其他语言的自我复制程序,请参见 Quine 页面 [1]。另请阅读关于自我复制程序工作原理的精彩解释 [2]。
[需要更新!]
试试这段代码
function save() local g={} foreachvar(function (n,v) %g[n]=v end) return g end function restore(g) foreach(g,setglobal) end
从 3.1 版本开始,您可以使用 C 中的 lua_open、lua_close 和 lua_setstate 来实现相同的功能,以及更多功能。
[需要更新!]
您必须遵循一个简单的协议:使用 lua_getparam 从 Lua 获取任何参数,使用适当的 lua_is... 函数确保它们类型正确,使用适当的 lua_get... 函数进行转换,对转换后的值做一些事情,并使用适当的 lua_push... 函数将结果推回 Lua。
让我们看一个实际的例子,并将 getenv 函数导出到 Lua。有关更多示例,请参见实现标准库的代码。
C 中的 getenv 函数接受一个字符串并返回另一个字符串。其原型是
char* getenv(char*);
因此,应调用的函数是 lua_isstring、lua_getstring 和 lua_pushstring
void wrap_getenv(void) {
lua_Object o=lua_getparam(1);
if (lua_isstring(o))
lua_pushstring(getenv(lua_getstring(o)));
else
lua_error("string expected in argument #1 to getenv");
}
从 3.0 版本开始,Lua 包含辅助函数,可以简化包装器的编写。使用这些函数,我们可以将 wrap_getenv 编写为
void wrap_getenv(void) {
lua_pushstring(getenv(luaL_check_string(1)));
}
编写好包装器后,您必须通过调用 lua_register("getenv",wrap_getenv) 使其对 Lua 可见。实际上,这几乎就是标准库所做的。
只需为您的库中的每个函数编写一个包装器,如上所述。
提供了多种解决方案来自动化此过程。请参阅 LuaAddons 上的“代码包装器”。
版本通知:这是 Lua 5 之前的版本。
除了像无限递归这样的微不足道的案例之外,当您在循环中从 C 调用 Lua 时,会收到此消息,因为参数和返回值会累积在 Lua 的堆栈中。例如,此代码将产生“堆栈大小溢出”
for (i=0;;i++) {
lua_pushnumber(i);
lua_call("print");
}
解决方案是将与 Lua 交互的代码用 lua_beginblock 和 lua_endblock 调用括起来
for (i=0;;i++) {
lua_beginblock();
lua_pushnumber(i);
lua_call("print");
lua_endblock();
}
引用参考手册的话:“强烈建议使用显式嵌套块。”