Lua5.1 Tips

Feb 9, 2016


Lua5.1 Tips

摘自《Lua程序设计(第二版)》中的一些例子、技巧。

虽然Lua5.3已经发布了,但由于cocos-lua因为tolua++和luajit的版本限制,还是使用Lua5.1,所以主要按Lua5.1学习。

代码在Lua5.3的编译器上运行,选取兼容方案,或记录版本差异,为将来可能发生的版本升级备案。


变量

  • lua允许下划线开头命名的变量,但由于下画线+大写字母的变量lua作为系统保留变量,而且单独的下划线用作哑变量(函数多返回值中不关心的部分),所以我个人不认为工程中不要用下划线开头的变量,除了表示接口与实现的函数名(如window()是接口,_window()是实现)。
  • 释放一个变量就是将nil赋值给它,然后GC可以自动释放它。
  • 字符串可以用单引号或双引号括起来,风格需统一,建议单引号,可以编辑更快。
  • bool变量只有false和nil表示假,其余都是真。
  • 字符串不以’\0’表示结尾,但占一个字节:
print (#'good\0bye') -- 8
print ('good\0bye') -- goodbye
  • string.format 格式化字符串,下面是其中%s和%q的区别,注意不要将带’\0’的字符串用于格式化,5.1和5.3的结果有差异
-- LUA5.1.4
--[[
ab
cd
--]]
string.format('%s', 'ab\ncd\0 new "q"')

--[[
"ab\
cd\000 new \"q\""
--]]
string.format('%q', 'ab\ncd\0 new "q"')

-- LUA5.3
--[[
ab
cd new "q"
--]]
string.format('%s', 'ab\ncd\0 new "q"')

--[[
"ab\
cd\0 new \"q\""
--]]
string.format('%q', 'ab\ncd\0 new "q"')

表达式

  • ’#’表达式,表示数组或字符串的长度。它可以用来方便的对数组操作:
print(a[#a]) -- 打印数组最后一个值
a[#a] = nil  -- 删除最后一个值
a[#a+1] = v  -- 在数组后面添加一个值,可用来循环给数组赋值
  • and or 逻辑操作符,并不是返回bool值,而是根据两端的真假返回两端本身的值。比如:
a = false or 1 -- 返回1
b = 3 and 2 -- 返回2
c = nil and 15 -- 返回nil
  • ’%’取余表达式规则: a % b = a - floor(a/b)*b。
    • 结果符号和被余数相同,可以对负数取余,可以对小数取余。
    • 可以这么想,对负数取余=与比自己大的最小余数整数倍差值。对正数取余=与比自己小的最大余数整数倍差值
    • 小数常用在取被余数的精度或精度之外的部分
print(-7 % -4) -- -3
print(-7 % 4)  -- 1
print(7 % -4)  -- -1
print(7 % 4)   -- 3
print(7 % 4)   -- 3
print(7.12 % 0.1)   -- 0.02
print(-7.12 % 0.1)   -- 0.08 要注意,这可能不是你的本意
print(7.12 - 7.12 % 1)   -- 7 取整
  • 可以为多个变量同时赋值,解释器先计算等号右边所有值再进行赋值。特殊用途:交换两个值:
a, b = b, a
  • 函数可以返回多个值,但只有没有运算的情况,且是一系列表达式的最后一个值才可以:
function fun()
  return 1, 2 
  -- return ((function () return 1, 2 end)()) -- 只能返回1,用括号强行返回一个结果
end

a, b = fun() -- a = 1, b = 2
a, b, c = fun(), 3 -- a = 1, b = 3, c = nil
a, b = (fun()) -- a = 1, b = nil 因为括号是表达式
t = {fun()} -- t = {1, 2}
t = {fun(), 3} -- t = {1, 3}
  • do end 可以声明一个作用域,可以用来包裹local变量,也可以用来在代码中间return和break。
-- 外面永远无法取得a的原本定义
a = function() return 1 end
do
  local b = a
  a = function() return 2 end
end

function() 
  -- return 'log' -- 编译器报错,因为下面还有语句
  
  do
    return 'log'  -- 正确,常用来调试
  end
  
  local b = 1
end
  • repeat … until … until语句中可以引用repeat作用域中声明的local变量
  • for i = 1, 10, 1 then 这种类型的循环,不要在循环中改变i的值来达到改变循环次数,这会引起逻辑错误,因为循环中取得的i是for中i的拷贝。而且i的上限和循环次数是先算好的,不是每次循环都计算。
  • for循环迭代器函数,可以返回三个值:遍历函数、常量(一般用作被索引的数据结构或一个table)、初始值,可以只返回遍历函数。
--[[
  for var_1, ..., var_n in <explist> do <block> end
  等价于
  do
  local _f, _s, _var = <explist>
    while true do
      local var_1, ..., var_n = _f(_s, _var)
      _var = var_1
      if _var == nil then
        break
      else
        <block>
      end
    end
  end
--]]

-- example
a = {}
for i=1,10,2 do
  a[i] = i + 10
end

function values (t)
  return function (state, current)
    current = current + 2
    if current <= state.max then
      return current, state.t[current]
    else
      return nil
    end
  end, {t = t, max = 7} , 1
end

for k,v in values (a) do
  print(k,v)
end

--[[ result:
  3	13
  5	15
  7	17
--]]


table

  • 数组的开始下标默认是1。
  • table作为数组,取得长度用”#”,不要用其他api诸如getn等,在lua5.1-5.3这些api都有变化。
  • table作为数组,下标要连续,不能出现中间值为nil的情况,否则如何遍历都找不到后面的值了。如果一定要用离散值,需要一个辅助table。
a = {}
for i=1,10,2 do
  a[i] = i + 10
end

-- 此时无法用#来取得总长度进行遍历,虽然table.maxn可以达到要求,但对于间隔大的离散值效率低,且5.3不兼容
print(#a, '\n') --因为下标为2的值是nil,所以 #a = 1
for i=1,#a do
  print(i, a[i], '\n') -- 此时只有一个值 1, 11
end

-- 辅助的迭代器,实质是辅助的table
function pairsByKeys (t)
  local b = {}
  for k in pairs(t) do
    b[#b+1] = k
  end
  table.sort(b)
  local i = 0
  return function()
    i = i + 1
    return b[i],t[b[i]]
  end
end

for k,v in pairsByKeys(a) do
  print(k, v);
end
  • table可以显式key声明:{x=0,y=0}等价{[‘x’]=0,[‘y’]=0},{‘a’,’b’,’c’}等价{[1]=’a’,[2]=’b’.[3]=’c’}。不要用[0]开始索引,某些API会出问题。
  • 所有数据结构都可以用table表示,效率很高。
  • string是不可变的,可变长的string可以用table表示,’..’操作符会复制整个字符串后再进行连接,可以用table.concat代替。
  • 若想被垃圾回收的对象,要手动置nil。例如:栈顶元素弹出后,除了栈顶索引减一外,还要把栈顶手动置nil,stack[top] = nil。
  • 弱引用table,只要key或value任意一个是弱引用且被回收了,整条记录都会被删掉。弱引用只会删除对象,整型、字符串都是值。
  • unpack,pack在5.3中是table.unpack和table.pack。unpack返回值数量上限取决于lua的返回值上限,现在是2k。
  • 面向对象编程时,经常出现class.__index = class; setmetatable(instance, class); 这种语句是为了表明class是个类型(实际是个table),instance是它的实现,更重要的是instance可以重写class的同名方法。

函数

  • 自递归定义问题
-- local fact = function (n) -- 报错

--  local fact            
--  fact = function (n)   -- 正确

local function fact(n) -- 正确,等同于上面
  if n == 0 then
    return 1
  else
    return n * fact(n - 1)
  end
end

-- 对于间接递归函数无效,必须使用前向声明
local f, g

g = function ()
end

f = function ()
end
  • lua对于尾调用(包含尾递归)有优化,正确使用不会出现栈溢出。必须如形式: return call(),不能对call的结果做运算。特殊场合可以用作状态机。