云风的个人空间 : Lua 编程技巧[LuaTips]

首页 :: 索引 :: 修订历史 :: 你好, 18.191.44.145
你的足迹: » Lua 编程技巧
Lua 的 5.1 版本已经正式发布。现在,我们应该把全部讨论放在这个版本上。

Hot! 应该尽量使用 local 变量而非 global 变量。这是 Lua 初学者最容易犯的错误。global 变量实际上是放在一张全局的 table 里的。global 变量实际上是利用一个 string (变量名作 key) 去访问这个 table 。虽然[InterWiki]Lua5 的 table 效率很高 ,但是相对于 local 变量,依然有很大的效率损失。local 变量是直接通过 Lua 的堆栈访问的。有些 global 变量的访问是不经意的,比如我们有双重循环操作一个迭代的 table:
for k1,v1 in pairs(tbl) do
    for k2,v2 in pairs(v1) do
        ...	
    end
end


这里,pairs 其实是一个全局变量应用的函数。如果我们这样做:
do
    local pairs=pairs
    for k1,v1 in pairs(tbl) do
        for k2,v2 in pairs(v1) do
            ...	
        end
    end
end


效率会稍微提高一些。如果是单层循环,这样做就没有意义。因为 for ... in 循环中的 pairs 这个函数只会被调用一次,而不是每次循环都去调。我们的原则其实是,被多次读取的 global 变量,都应该提取出来放到 local 变量中。

Hot! 警惕临时变量 字符串的连接操作,会产生新的对象。这是由 lua 本身的 string 管理机制导致的。lua 在 VM 内对相同的 string 永远只保留一份唯一 copy ,这样,所有字符串比较就可以简化为地址比较。这也是 lua 的 table 工作很快的原因之一。这种 string 管理的策略,跟 java 等一样,所以跟 java 一样,应该尽量避免在循环内不断的连接字符串,比如 a = a..x 这样。每次运行,都很可能会生成一份新的 copy 。

同样,记住,每次构造一份 table 都会多一份 table 的 copy 。比如在 lua 里,把平面坐标封装成 { x, y } 用于参数传递,就需要考虑这个问题。每次你想构造一个坐标对象传递给一个函数,{ 10,20 }  这样明确的写出,都会构造一个新的 table 出来。要么,我们想办法考虑 table 的重用;要么,干脆用 x,y 两个参数传递坐标。

同样需要注意的是以 function foo (...) 这种方式定义函数, ... 这种不定参数,每次调用的时候都会被定义出一个 table 存放不定数量的参数。

这些临时构造的对象往往要到 gc 的时候才被回收,过于频繁的 gc 有时候正是效率瓶颈。

Hot! 使用 closure 代替 table 上面提到封装坐标的问题。诚然,我们可以用 { x=1,y=2 } 这样封装一个坐标。不过还有一个方法可供选择。它稍微轻量一点。

function point (x,y)
	return function () return x,y end
end
 
-- 使用范例
p=point(1,2)
print(p())
-- 输出 1	 2


如果你愿意,还可以做的复杂一点:
function point (x,y)
	return function (idx) 
		if idx=="x" then return x
		elseif idx=="y" then return y
		else return x,y end
	end
end
 
-- 使用范例
p=point(1,2)
print(p("x"))		-- 1
print(p("y"))		-- 2


x,y 实际被存放在 closure 里,每次调用 function point 都有一份独立的 closure。当然,function 的 code 只有一份。

Hot! 设法减少从 C 向 Lua 传递字符串 字符串常量在 Lua VM 内部工作的非常快,但是一个从 C 向 lua vm 通过 lua_pushstring 之类的 api 传递进 VM 时,就需要掂量一下了。这至少包含一个再 hash 和匹配的过程。[InterWiki]我的 Blog 上的一篇文章讨论了这个问题

Hot! lua 中的继承 lua 中实现 OO ,虚表往往设置一个 metatable 并设置 __index ,而继承则用 metatable 的 __index 把虚表串起来。当类继承层次过多的时候,效率比较低,那么就可以用下面这个技巧。
function inherit(sub,super)
     setmetatable(sub,
     { __index=function(t,k)
             local ret=super[k]
             sub[k]=ret
             return ret
     end } )
end


Hot! 利用逻辑运算的短路效应 lua 编程中,and or 跟 C 一样是有短路效应的,不过他们的返回值并非 bool 类型,而是表达式中的左值或者右值。我们常常利用这个特性来简化代码。
function foo(arg)
     arg=arg or "default"
     ...
end

利用 or 运算赋缺省值是最常用的技巧。上例中,如果 arg 为 nil ,arg 就会被赋值为 "default" 。但是这个技巧有个缺陷,当缺省值是 true 的时候会有点问题。
a=a or true    -- 错误的写法,当 a 明确写为 false 的时候,也会被改变成 true 。
a= a ~= false    -- 正确的写法,当 a 为 nil 的时候,被赋值为 true ;而 false 则不变。


另外,巧妙使用 and or 还可以实现类似 C 语言中的 ?: 三元操作:
function max(a,b)
    return a>b and a or b
end

上面这个函数可以返回 a 和 b 中较大的一个,其逻辑类似 C 语言中的 return (a>b) ? a : b ;

Hot! 模拟 pascal 中的 with pascal(delphi) vb 中都有一个 with 的关键字,可以帮助程序员减少大量的输入,并能提高效率。lua 中我们可以利用以下方式模拟。
a={}
 
setfenv(function()
    a=1
    b=2
end,a)()
 
-- 以上代码等价于
 
a.a=1
a.b=2