退休的 Lua 常见问题解答 |
|
你不需要:Lua 没有也不需要变量声明。
但是为什么你需要声明变量?
有些人想要声明变量来保护自己免受打字错误的影响。例如,代码
color="red" print(colour)
将打印 nil
因为第 2 行中 color
被误写为 colour
,而 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?
包类似于集合,只是元素可能出现多次。包的实现方式与集合类似,但使用与元素关联的值作为其计数器。以下是包的代码片段。
-- 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 上的“代码包装器”。
版本说明:这是 Lua5 之前的版本。
除了琐碎的情况(例如无限递归)之外,当你从 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(); }
引用参考手册:强烈建议使用显式嵌套块。