lua

Lua 笔记

Posted on November 18, 2014

1. Getting Starting

Chunks

  • Lua 语句之间无需分隔符, 分号可选, 通常只在一行中有多个语句时才使用分号

  • To exit the interactive mode and the interpreter, just type the end-of-file control character ( ctrl+D in UNIX, ctrl+Z❩ in Windows)

  • You can use the -i option to instruct Lua to start an interactive session after running the given chunk

    %lua -i hello_world.lua 表明执行完此文件后打开交互Lua, 对调试非常有用

  • Another way to run chunks is with thedofile function, which immediately executes a file

    如在交互Lua中> dofile("path_to_your_luafile.lua") 该文件就会加载到交互Lua中

Lexical Conventions

  • You should avoid identifiers starting with an underscore followed by one or more upper-case letters (e.g. _VERSION ); they are reserved for special uses in Lua
  • 注释 单行注释: -- 块注释:

    --[[
    print(10) --不会执行
    --]]
    

    取消块注释技巧

    ---[[
    print(10) --会执行
    --]]
    

Global Variables

  • Global variables do not need declarations; you simply use them

    未声明的变量直接使用也不会报错, 只是返回nilprint(abc) -->nil

  • If you assign nil to a global variable, Lua behaves as if the variable had never been used

    给变量赋值nil将回收此变量内存

The Stand-Alone Interpreter

  • 文件首行以#!/usr/bin/env lua 开头, 可以让lua解释器直接执行此文件: lua [options] [script [args]]

  • lua -e "print('hello world')" 和ruby一样

  • lua -i [文件或者-e "语句"] 执行后进入交互模式

  • -l 加载一个lib, -l 选项会调用 require

    lua -i -la -lb 执行完a文件和b文件后, 进入交互模式

  • 交互模式下的print: > =math.sin(3) 前面有等号(貌似就是关键字return), 会打印出执行结果, 否则执行了不会有结果

  • LUA_INIT_5_2 LUA_INIT Lua 会查找环境变量 LUA_INIT 的值,如果变量存在并且值为@filename,Lua 将加载指定文件。如果变量存在但不是以@开头,Lua 假定 filename 为 Lua 代码文件并且运行他

  • Lua 使用所有参数构造 arg 表。脚本名索引为 0,脚本的参数从 1 开始

    lua script a b c arg[0] script arg[1] a …


2. Types and Values

  • 八种基本类型: nil, boolean, number, string, userdata, function, thread, table

    函数 type(value) 可以返回value的类型, 此方法返回值是个string

  • nil

    a global variable has a nil value by default, before its first assignment

    you can assign nil to a global variable to delete it

  • boolean

    The boolean type has two values, false and true

    in Lua, any value can represent a condition

    条件判断中, false, nil 将是 false, 其他的都是true

  • number

    The number type represents real (double-precision floating-point) numbers. Lua has no integer type

  • string

    string 不可以变

    取长度操作#print(#_VERSION)

    字符串字面量: 单双引号均可, 单双引号内都支持转移字符(反斜杠转移)

    长字符串字面量可以使用[[ ]] 进行定义, 为了避免和内容冲突, 还可以在其中添加任意的=, 如[==[ ]==]

    对字符串执行数字的操作, 将会进行字符串转换到数字: print('5' +6) => 11

    同样数字也可以转为为字符串: print(10 .. 20) => '1020'

    但是比较运算不会进行转换: print('11' == 11) => false

    最佳实践: 不要依赖自动转换

    显示的转换: tonumber('2') => 2 ` tonumber(‘x2’) => nil tostring(2) => ‘2’ tostring(nil) => ‘nil’`

  • table

    table是object, 是可以用字符串, 数据进行索引的对象

    key可以是除了nil外的其他所有类型的值, key可以是混合不同类型, ‘1’, 1, ‘01’ 都是不同的key

    key 可以是字符串(record)或者数字(list)甚至对象, 如果key需要是表达式计算,用[]

    对于字符串的key, 对table键值对的读写类似javascript: 可以用table_object[key_name]也可以用table_object.key_name

    赋值nil给指定key, 用于删除此key.

    用table表示数组: 无法显式声明长度, 索引惯例是从1开始, 对于长度, 有的做法会将其存到nkey上.

    # 返回table最后的连续数字索引

    构造

    list 字面量: a = {10, 20, 'fox'}

    table字面量: a = {x=10, y=20}

    混合型:

    a = {
      x = 'm',
      y = 'n',
      {x = 0},             --a[1]
      {['x'..'y'] = 1}     --动态计算的key
    }
    

    you can always use a semicolon instead of a comma in a constructor. I usually reserve semicolons to delimit different sections in a constructor

    {x=10, y=20; "one", "two"}

    每次调用构造函数,Lua 都会创建一个新的 table,可以使用 table 构造一个 list

    list = nil
    for line in io.lines() do
      list = {next=list, value=line}
    end
    

    理解:以上代码能起作用, 说明list存于next时, 直接存的是对象地址, 虽然传的是对象, 但是对象先求值(对象地址)

  • function

    functions are first-class values in Lua

    Lua can call functions written in Lua and functions written in C

  • Userdata and Threads TODO


3. Expressions

  • 比较: <, >, <=, >=, ==, ~=

    ==, ~=可以对于不同类型的比较 不同类型的值会视为不等, nil只等于自己本身

    <, >, <=, >= 只能用于2个string或者2个number, 不同类型大小比较将报错

    Lua compares tables and userdata by reference

  • 逻辑运算

    and or 短路运算, 返回值, 而不是返回逻辑结果

    not 总是返回bool值

    技巧:

    空值保护: x = x or y 等价于 if not x then x = y end

    三元运算: x and y or z 等价于其他语言中的 x ? y : z (lua中没有三元运算)

  • 连接

    .. 因为字符串不可变, 连接会创建新的字符串

  • 长度

    #

    a sequence is a table where the numeric keys comprise a set 1 … n.

    the length operator is unpredictable for lists with holes (nils). It only works for sequences, which we defined as lists without holes.

    Remember that any key with value nil is actually not in the table.

    In particular, a table with no numeric keys is a sequence with length zero


4. statements

  • 支持多重赋值: a, b = 10, 20 可用于变量交换
  • 多重赋值更慢
  • 多重赋值主要用于交换变量, 以及接收函数的多个返回
  • local variables have their scope limited to the block where they are declared. 代码块:指一个控制结构内,一个函数体,或者一个 chunk(变量被声明的那个文件或者文本串) 因此交互命令下的一句local a=2执行之后立即过期, 可以用一个do...end解决
  • It is good programming style to use local variables whenever possible

Control Structures

  • if then end if then else end if then elseif then else end while do end repeat until ...(lua中的do while)

  • Unlike in most other languages, in Lua the scope of a local variable declared inside the loop includes the condition 比如在repeat里定义的local变量, 在后面的until表达式里还是可见

  • numeric for: for 循环变量 = 起始表达式, 终止表达式, [递增表达式, 可选, 默认为1] do end

    • 循环变量自动声明为局部变量(没有显示的local), 并且只在循环内有效
    • 运行条件是: 循环变量<=终止表达式
    • If you want a loop without an upper limit, you can use the constant math.huge
    • all three expressions are evaluated once, before the loop starts
    • you should never change the value of the control variable
  • Generic for

    • the control variable is a local variable automatically declared by the for statement, and it is visible only inside the loop
    • you should never change the value of the control variable
    • pairs 用于遍历一个table, 如 for k, v in pairs(a) do print(k, v) end
    • 迭代器: io.lines pairs ipairs string.gmatch

      pairs 会迭代所有键值对, 包括数字索引(数组)和其他所有 ipairs 只遍历从1开始的连续数字索引

  • goto TODO


5. function

  • 每个函数都有自动的return (返回nil,类似javascript, 而不是像ruby返回最后一个表达式)

  • Lua 语法要求 break 和 return 只能出现在 block 的结尾一句(也就是说:作为 chunk的最后一句,或者在 end 之前,或者 else 前,或者 until 前)

    如果要非要在中间return, 需要用do end包起来: do return end (do end 可以单独使用, 以定义一个代码块)

  • 调用时候的括号:

    • 如果调用时无参数, 必须有括号
    • 如果只有一个参数, 且参数是字符串或者table, 则可以省略括号
  • o:foo(x)o.foo(o, x) 等价

  • Lua 函数实参和形参的匹配与赋值语句类似,多余部分被忽略,缺少部分用 nil 补足, 不可以设置默认值

  • 函数声明没有默认值设定机制, 只能在函数中: n = n or 1

  • Multiple Results: 如string.find(origin, target) 返回开始索引和结束索引(索引)

  • When a function call is the last (or the only) argument to another call, all results from the first call go as arguments

    如果做为另一个函数的参数的函数调用不是最后一个参数的话, 此函数返回值将只有第一个

  • 如果函数调用是在表达式中, Lua adjusts the number of results to one

  • A constructor also collects all results from a call, without any adjustments: t={function_return_many()}

    As always, this behavior happens only when the call is the last expression in the list; calls in any other position produce exactly one result: t={funciton_return_many_but_only_get_one(), 1}

  • You can force a call to return exactly one result by enclosing it in an extra pair of parentheses: print((funciton_return_many_but_only_get_one()))

  • table.unpack receives an array and returns as results all elements from the array, starting from index 1

    it uses the length operator to know how many elements to return, 但你也可以手动指定需要的范围: print(table.unpack({10,20,30,40}, 2, 3)) => 20, 30 左闭右闭

    类似ruby中func(*input_array)

  • 可变参数函数: function name(...) 三个点代表可变参数(书上叫做extra arguments)

    Lua将函数的参数放在一个叫arg的表中,除了参数以外,arg表中还有一个域n表示参数的个数, 参见http://book.luaer.cn/_38.htm

    We call the expression ... a vararg expression. It behaves like a multiple return function, returning all extra arguments of the current function

    {...}返回一个table, 但是去掉了其后置nil的值(如果传入参数有后置nil的话)

    table.pack(...)也返回一个table, 也去掉了nil, 但是带有一个n属性, n的长度是包括了nil的长度for k, v in pairs({1,2; a=nil, b=8}) do print(k, v) end 输出没有a

    去掉后缀nil和命名nil其实是table自带的功能

  • Variadic functions can have any number of fixed parameters before the variadic part: function fwrite(fmt, ...)

  • lua不支持命名参数, 可以通过传入table来模拟

6. More about Functions

  • 函数名实际是指向函数的一个变量

    定义函数的两种方式,和javascript类似

    [local] function foo (x) return 2*x end

    [local] foo = function (x) return 2*x end

    函数作为对象属性:

    Lib.foo = function (x,y)...

    或者

    Lib = { foo = function (x,y)... }

    或者

    Lib = {}; function Lib.foo (x,y)...

  • function是一等对象, with proper lexical scoping(It means that functions can access variables of their enclosing functions)

    闭包是一个函数加上它可以正确访问的 upvalues(外部的局部变量 external local variable)

  • 函数是匿名的, function foo() end 等价于foo = function() end, 构造一个函数, 赋值给变量

  • 高阶函数: 接收函数作为参数的函数

迭代器

  • 闭包是一个内部函数,它可以访问一个或者多个外部函数的外部局部变量。每次闭包的成功调用后这些外部局部变量都保存他们的值(状态)。当然如果要创建一个闭包必须要创建其外部局部变量。所以一个典型的闭包的结构包含两个函数:一个是闭包自己;另一个是工厂(创建闭包的函数)

编译·运行·调试

  • dofile:

    调用了loadfile, 如果 loadfile 失败, dofile 使用 assert 抛出错误

    无加载记录, 每次重复执行

  • loadfile:

    容错函数

    编译代码成中间码并且返回编译后的 chunk 作为一个函数,而不执行代码

    发生错误的情况下, 返回 nil 和错误信息

    运行一个文件多次的话,loadfile 只需要编译一次,但可多次运行。dofile 却每次都要编译 TODO

    (推测)总是在全局环境中编译他的串

  • loadstring

    编译代码成中间码并且返回编译后的 chunk 作为一个函数,而不执行代码

    发生错误的情况下, 返回 nil 和错误信息

    总是在全局环境中编译他的串

  • require

    类似dofile, 区别:

    Lua将require搜索的模式字符串放在变量package.path中。当Lua启动后,便以环境变量LUA_PATH的值来初始化这个变量。如果没有找到该环境变量,则使用一个编译时定义的默认路径来初始化。如果require无法找到与模块名相符的Lua文件,就会找C程序库。C程序库的搜索模式存放在变量package.cpath中。而这个变量则是通过环境变量LUA_CPATH来初始化的

    先搜索package.path 如果找到用loadlib 加载; 如果没找到, 搜索package.cpath 如果找到用loadlib加载

    require 会判断是否文件已经加载避免重复加载同一文件, 在package.loaded 记录了所有加载过的模块(模块有返回值则记录返回值, 如果没有返回值则记录true)

    一个文件将被作为一个function来执行加载, 因此文件中的局部变量, 函数将对外不可见, 全局变量,函数对外可见. 可以在文件最后加上return返回想要输出的对象

    require will change each interrogation mark in the template by filename,which is modname with each dot replaced by a “directory separator”(such as “/” in Unix))

    传入require中的dot, 会被解释成为目录分隔符


错误处理

  • error (message [, level]) 抛出异常, 类似ruby raise, level 指的是引发错误的函数调用栈层

  • 函数错误通常指导原则: 易于避免的异常引发一个错误(调用error), 否则返回nil

  • 多数函数设计的返回值是: value, message = callsome() 我把这种函数叫做容错函数

    如果发生错误, value 为nil, message 为错误消息; 如果没有错误, value为正常返回值

  • assert(dosomething, message) 类似于 dosomething ? dosomething : error(message)

    lua常见技巧: result = assert(容错函数调用); 期望得到正确的返回值, 否则就抛异常

  • lua中的try-catch: pcall

    status, error_or_result = pcall(somefunction)

    成功的话, status是true, error_or_result是somefunction的返回值

    发生异常的话, status是false, error_or_result是错误消息, 不一定是字符串, 是抛出异常时调用error时的参数

  • print(debug.traceback()) 任何时刻手动打印当前调用栈


metatables

  • table 和userdata 的实例可以有各自不同的metatable, 其他类型的值共享其类型所属的单一metatable

  • lua 代码中只能设置table的metatable

  • 字符串默认设置了metatable, 其他类型默认没有

  • Lua 选择 metamethod 的原则:如果第一个参数存在带有__add 域的 metatable,Lua使用它作为 metamethod,和第二个参数无关;

    否则第二个参数存在带有__add 域的 metatable, Lua 使用它作为 metamethod 否则报错

    算术运算不关心混合类型的, 只要符合以上两条规则之一即可

    关系元算不支持混合类型运算,如果你试图比较一个字符串和一个数字,Lua 将抛出错误.相似的,如果你试图比较两个带有不同 metamethods 的对象,Lua 也将抛出错误

  • 库定义的metamethods

    print 函数总是调用 tostring 来格式化它的输出。然而当格式化一个对象的时候,tostring 会首先检查对象是否存在一个带有__tostring 域的 metatable

    setmetatable/getmetatable 函数也会使用metafield ,在这种情况下 ,可以保护metatables。

    假定你想保护你的集合使其使用者既看不到也不能修改 metatables。如果你对metatable设置了__metatable 的值,getmetatable将返回这个域的值,而调用setmetatable将会出错

  • 表相关的metamethods

    __index 不需要非是一个函数,他也可以是一个表。但它是一个函数的时候,Lua将table和缺少的域作为参数调用这个函数; 当他是一个表的时候, Lua将在这个表中看是否有缺少的域

    -- create a namespace
    Window = {}
    
    -- create the prototype with default values
    Window.prototype = { x=0, y=0, width=100, height=100 }
    
    -- create a metatable
    Window.mt = {}
    
    -- declare the constructor function
    function Window.new (o)
      setmetatable(o, Window.mt)
      return o
    end
    
    Window.mt.__index = function (table, key)
      return Window.prototype[key]
    end
    
    w = Window.new{x=10, y=20}
    print(w.width) --> 100
    

    以上lua代码几乎就是实现了javascript原型链集成, 一个明显的区别是setmetatable(o, Window.mt) 在javascript中是语言实现的, lua 需要编码者实现的

    __newindex metamethod 用来对表更新, 当你给表的一个缺少的域赋值,解释器就会查找__newindex metamethod, 如果存在则调用这个函数而不进行赋值操作。

    像__index 一样,如果 metamethod 是一个表,解释器对指定的那个表, 而不是原始的表进行赋值操作。(这里和js有点区别, js改属性不涉及原型, 但是lua却可能)

  • rawset(table, key, value) rawget(table, key) 可以跳过metamethod

  • 算术元方法: __add __mul __sub __div __unm __mod __pow

  • 连接操作: __concat

  • 关系元方法: __eq __lt __le

  • tostring: __tostring(this) 对print有效

  • __metatable 设置此值, 用于禁止元表的读写

  • __index(table, key) 可以是一个函数或者table, 当读取table的key不存在时, 调用此方法/table

  • __newindex(table, key, value) 可以是一个函数或者table, 当设置table的key不存在时, 调用此方法/table


环境

  • 全局变量属于_G

Packages

模块文件约定是返回一个全局的table, 模块文件如果没有显示return, 返回值将是true

包定义的几种格式:

  • 最原始: 写死包名, 完整包名为内部调用命名空间

    complex = {}
    function complex.new (r, i) return {r=r, i=i} end
    -- defines a constant `i'
    complex.i = complex.new(0, 1)
    function complex.add (c1, c2)
       return complex.new(c1.r + c2.r, c1.i + c2.i)
    end
    function complex.sub (c1, c2)
       return complex.new(c1.r - c2.r, c1.i - c2.i)
    end
    return complex
    
  • 统一内部调用命名空间(不依赖固定包名)

     local P = {}
     complex = P -- package name
     P.i = {r=0, i=1}
     function P.new (r, i) return {r=r, i=i} end function P.add (c1, c2)
        return P.new(c1.r + c2.r, c1.i + c2.i)
     end
     return complex
    
  • (文件名)动态模块名称, 便于模块改名

    --将模块名设置为require的参数,这样今后重命名模块时,只需重命名文件名即可。
    local modname = ...
    local M = {}
    _G[modname] = M
    
    M.i = {r = 0, i = 1}  --定义一个模块内的常量。
    function M.new(r,i) return {r = r, i = i} end
    function M.add(c1,c2)
       return M.new(c1.r + c2.r,c1.i + c2.i)
    end
    
    function M.sub(c1,c2)
       return M.new(c1.r - c2.r,c1.i - c2.i)
    end
    --返回和模块对应的table。
    return M
    
  • 去掉内部命名空间前缀(javascript)

    local function new (r, i) return {r=r, i=i} end
    local function add (c1, c2)
       checkComplex(c1);
       checkComplex(c2);
       return new(c1.r + c2.r, c1.i + c2.i)
    end
    ...
    --export
    complex = {
       new = new,
       add = add,
       sub = sub,
       mul = mul,
       div = div,
    }
    return complex
    
  • (setfenv TODO) 5.2 已经去除


其他