在我使用Lua编程整整9个月后,是时候停下来反省一下这段经历了. 过去了几年里,我使用了各式各样的语言:Perl (soaplite.com,还有其它的项目,包括我现在的咨询工作),C (DHCPLite 和 ping-pong juggling robot),JavaScript (Google Maps相关经验 和 canvas), MATLAB (MobDebug),扩展了一个LuaIDE (ZeroBrane Studio),一个移动应用程序 (LuaRemote),一些教育性的脚本 (EduPack),还有一个使用Lua在浏览器画板上进行绘图的demo.
虽然我已经见过很多提到Lua的好和坏的列表 (例如, Lua的优势, 为什么使用Lua, 为什么Lua没有得到广泛地应用, Lua的优点,Lua的好和坏, Lua对比JavaScript,还有Lua的陷阱),但是有些特性坑死爹了,还有些他们忘了提,所以我就自己搞了个列表. 虽然这说不上非常专业,也没有覆盖到语言的每个方面 (如 math 和string 库),但这是根据我的编程语言经历得出来的.
好的 小巧: 20000行C代码 可以编译进182K的可执行文件 (linux下). 可移植: 只要是有ANSI C 编译器的平台都可以编译. 你可以看到它可以在几乎所有的平台上运行:从 microcontrollerslua
. 先天的协程支持,用于实现 迭代器 和非抢占式多线程. 低延迟的增量垃圾回收,没有额外的内存开销,低实现复杂度,并且支持 weak tables. 强大并多样化的表 可以保存任意类型的数据 (除了 nil
),还可以使用任意类型的值进行索引 (除了 nil
): {1,2,5,foo = "bar",[func] = "something",["some spaces"] = value()}
. 词法作用域. 一流的函数 和 闭包 支持的 函数式编程. 尾调用: return functioncall()
. 递归函数不需要事先声明: local function foo() ... foo() ... end
; 注意这样不行 local foo = function() ... foo() ... end
. 函数返回 多个值: return 1,3
. 调用者可以认为返回值是任意个数的: 如果多于3个,其余会被丢弃; 如果少于3个,那其它的会是未初始化的 nil
. 函数允许变化的变量个数, function foo(...) local args = {...}; bar(param,...) end
. table可以 "拆包" 成参数列表,unpack
(或 Lua 5.2的 table.unpack
): print(unpack({1,3}))
打印1 2 3
. *** 作环境变量 (Lua 5.1中的getfenv
和setfenv
和Lua 5.2中的_ENV
*** 作),此外还可以构造 沙盒 . 同时赋值多个变量: local a,b,c = 1,2
,243); line-height:1.2">x,y = y,x,or a,b = foo()
. 多行字符串 (using [[...]]
; 可以使用 [[...[=[...]=]...]]
)包含和注释 (--[[...]]
). 可选的分号语句分隔符 (多数用于解决模棱两可的的情况 a = f; (g).x(a)
). 重载使用 metatables. 元编程 可以根据你的 DSL修改抽象语法树来创造新的语法. for
语句有两种形式: generic (使用迭代器: for a in iter() do ... end
) 和 numeric (使用数字: for a = 1,0.1 do ... end
); 数字的这个支持各种类型的步进 (不仅仅是整数). 函数调用的语法糖 (f'string'
,243); line-height:1.2">f"string",243); line-height:1.2">f[[string]],andf{table}
)和方法调用(obj:m()
). 简单而强大的 调试 库. @H_502_166@ 与众不同的 表和字符串索引从1而不是0开始. 对一个表中的值赋 nil
会从表中删除它. 这就是说对于不存在的值返回 nil
,所以元素存不存在跟它是不是 nil
是同一个问题. a = {b = nil}
产生一个空表. 没有独立的整数类型; 数字类型 表示的是实数. 没有类; 面向对象 使用 表 和 函数实现; 继承使用 metatable 机制实现. 方法调用使用 object:method(args)
的写法,与 object.method(object,args)
的写法是等价的,但 object
只取值一次. nil
和false
是仅有的表示假的值; 0,0.0,"0" 等其它的一切值都是true
. 不等于是 ~= (例如,243); line-height:1.2">if a ~= 1 then ... end). not,or,and
*** 作符是逻辑运算符. 赋值是语句,这就意味着没有 a=b=1
或if (a=1) then ... end
的写法. 没有 a+=1
,243); line-height:1.2">a++,或其它简写形式. 没有 continue
语句,尽管有一个 解释 和一堆的替代品,如在循环中使用 repeat break until true
跳出 或者使用一个Lua 5.2中的goto
语句. 没有 switch
语句. 某些上下文可能会用到括号; 例如,243); line-height:1.2">a = {}; a.fIEld 正常,但{}.fIEld
不行; 后者需要这样写 ({}).fIEld
. 循环的控制变量默认是局部的, 循环完了就没了. for
循环中的极限和步进值是 缓存过的; 这意味着 for i = init(),limit(),step() do ... end
中的三个函数 init
,243); line-height:1.2">limit,和step
只在循环开前调用过一次. 条件 和其它控制语言不需要括号. 字符串和数字会自动转换 (需要一个数字时提供一个字符串,反之亦然),除了相等比较: 0 == "0"
为false
,243); line-height:1.2">{} ~= 1 为 true
,还有foo["0"]
和foo[0]
引用的是表中不同的值; 其它关系运算符会在比较不同类型的值时产生错误. 逗号和分号 都可以作为表中的元素分隔符; 也同样都可以作为 可选的分隔符 放在结束括号前: a = {a = 1,b = 2,}
. 比想像中还要少的内部组件; 可能一些人觉得这就像 "电池没有包含在内"一样. 从另一个角度来看,这成就了它的紧凑而又可移植的核心,不过同时有一些库可以进行补偿,如 LuaRocks 和Penlight. @H_502_166@ 坏的 有限的错误处理支持 (使用pcall 和xpcall),尽管有些人 争论这已经够用了 ,只需要加一些语法糖和特性支持 (如确定性的finalizer). pcall
和error
的组合十分强大,特别是 error
可以返回任何东西 (例如一个表)而不是仅仅是一个字符串,但是 catch ... finally
结构在多数情况下可能更加清晰直观. 默认是全局的作用域 (这么说对 Lua 5.2不公平,它已经没有全局了). 有一个 strict 模块要求所有全局变量都需要初始化. 虽然我并没有很多问题是由未初始化的全局变量引起的,但还是把它放到"坏的"分类,因为有一次我犯了一个错误,在调用一个"next"变量时没有局部化它, 引起一个问题 就是迭代器覆盖了另一个模块的next 函数. 没有Unicode 支持 (最起码string.len
和模式识别函数需要识别 Unicode 字符); 不过有一个ICU库的 绑定 实现了Unicode支持. 可以看一下这条 消息 和后续总结的的关于现有的支持和string.*
需要什么样的修改. 有限的模式匹配支持,尽管已有的也十分强大.在使用了15 年 Perl后,我非常想念其中的一些正则表达式特性(多数是前向搜索,可选组 (group )?
,还有组内组),没有任何一个都是会增加实现复杂度的. 对于需要更强大的正则表达式的人可以使用 LPeg 和它的 re 模块. 没有三目运算符; 有一些替代品. 一般我使用 foo = test and value1 or value2
形式, value2
在test
和value1
都为 false
时可以赋值. 没有内置POSIX函数. 虽然有 luaposix 模块,但是它需要编译,这并不是一个好的选择. 尽管对于这个我并没有很强的需求,但是每当我需要获取/设置一个环境变量时总会直观想到去访问 getenv
和setenv
[6/1/2012更新] miko 在评论中提到,有 os.getenv,但是没有相应的 os.setenv
. 没有类/对象 finalizer. Lua 通过 __gc metamethod提供finalizer 的功能 ,但它只能用于自定义类型 (不是表),并且不能跟其它语言的相应功能匹配,举例来说, Perl中的 DESTROY 和 END方法. [05/27/2012更新] Lua 5.1中有一个没有文档说明的 newproxy特性,它实现了表的 finalizers; Lua 5.2 移除了这个特性的同时增加了 表的__gc元方法. 没有Lua和C代码之间的yIElding: coroutine.yIEld
在跨越 Lua/C 边界调用时会失败 attempt to yIEld across Metamethod/C-call boundary
. 我在使用 luasocket和协程进行异步编辑时多次遇到过这个错误,最后使用 copas 模块解决. 在Lua 5.2中这个问题得到解决. @H_502_166@ 坑爹的 表中元素的个数并不是很容易获取,结果取决于你怎么做 (或你怎么定义"长度"). 这可能不是个意外,因为Lua提供了强大的表并支持灵活的索引方式 (数字或其它Lua类型,除了 nil
). Lua中的表有两部分: "数组" 部分(使用 t = {1,3}
生成) 和 "哈希" 部分(使用t = {a = "foo",["b"] = 2}
生成); 这两者可以灵活地结合在一起. #table
返回最短的"数组"部分长度(没有任何缺口) 而table.maxn(t)
返回最长的 "数组" 部分(Lua 5.2移除了这个函数). "哈希" 部分没有定义长度. 两者都可以使用 pairs
方法进行遍历,同时允许你对其中的元素进行计数. 然而,243); line-height:1.2">print(#{1,nil,3}) 打印4 却不是想像中的 2,3,nil}) 打印的则是2. 我确信有一个合理的理由解释它,但是现在说是就是"坑爹"的地方. [11/17/2012更新] FireFly 在评论中提到,Lua 5.2 中表的长度 只定义成 没有洞的. return
必须是语句块中的最后一句; 也就是说,243); line-height:1.2">function foo() print(1); return; print(2) end 会触发一个错误 'end' expected...
或 unexpected symbol near <whatever statement you have after 'return'>
(这取决于在return
之后有没有分号). 没有人会这样写,除非你在调试,但我却被它坑了好几次. 原本我想把它放进"与众不同的"分类,但是我发现它前后矛盾. 在一个不能使用 return
的地方却能使用 do return end
. [5/19/2012更新] 这同样出现在 break
语句上,虽然在Lua 5.2中 break
不再必须是语句块的最后一句了. 函数只返回一个值但它并不是列表中的最后一个; 如: function f123() return 1,3 end function f456() return 4,6 end print(f123(),f456()) -- prints 1,4,6 print(f456(),f123()) -- prints 4,1,3这个
return
的行为也受到这条规则约束: return f456()
返回3个值,243); line-height:1.2">return (f456()) 只返回一个值 (注意多出的括号). 关于这个语言特性有 很好的文档,但我仍然认为它太坑爹了 (或许在旁人看来它是优点). 总的来说,到目前为止我很享受这个语言带来的简洁和便利,尽管有些东西跟我之前的做法有点不一样. 特别是在8岁的儿子很快地学会了Lua的语法后,我觉得自己的那些关于 Turbo Pascal 的经历已经过时了.
总结以上是内存溢出为你收集整理的Lua: 好的, 坏的, 和坑爹的全部内容,希望文章能够帮你解决Lua: 好的, 坏的, 和坑爹的所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)