退休的 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();
}
引用参考手册:强烈建议使用显式嵌套块。