基础特性

lua的学习资源

数据类型

  • nil

a=nil 等效为删除a。未赋值的区域是nil的海洋

  • boolean

只有 nil 和 false 会被判断为假

  • number

lua5.3引入了整数,其类型也是number,但支持位操作。例如 2 | 3 = 3; 1 & 2=0;

  • string

string.char(97)       --'a'
string.byte('a')      -- 97
string.format("my name is:%s" , nameStr) --格式化输出
table.concat({"a","b","cd"} , "-")       --字符串拼接"a-b-cd"

-- 字符串分割比较麻烦,见 http://lua-users.org/wiki/SplitJoin
  • thread

lua的 thread 其实是协程。其和真正的线程差不多,拥有独立的栈、局部变量和指针,可以和其他thread共享全局变量。但是,真正的线程可以同时多个运行,而协程任意时刻只能运行一个。

  • userdata

数据结构

  • table

lua唯一的数据结构。

变量

  • 默认为全局,需要显式声明为 local 才为局部变量。

  • 访问局部变量更快。如有必要,可将全局变量的引用保存到局部,加快访问速度,也可同步更改全局变量的值。

函数

  • 可作为参数传递

  • 支持匿名函数

  • 默认为 public 函数,用local声明则为该模块的私有函数

  • 【语法糖】func({1,2,3})可简写为func{1,2,3}

  • 【语法糖】

    • 定义时。function t:func(p1,p2) 等效为 function t.func(self,p1,p2)

    • 调用时。t:func(1,2) 等效为 t.func(t,1,2)

模块与包

  • 模块(module)就是一个定义了一个table的文件,最后 return 该模块名称。

  • 包(package)就是一系列的模块(module)

  • require "module_name",会先从package.loaded中找,再用lua加载器从package.path中找,再用c加载器从package.cpath中找,再用一体化加载器

环境

  • 引用一个叫 var 的自由名字(指在任何层级都未被声明的名字)在句法上都被翻译为 _ENV.var 。 此外,每个被编译的 Lua 代码块都会有一个外部的局部变量叫 _ENV。

  • _ENV是一个table,被称为环境

  • 全局环境叫做_G,被保存在c注册表的一个特别索引下。而且,_G._G == _G。

  • 所有的标准库都被加载入全局环境

  • lua53去掉了setfenv和getfenv,转而用_ENV和upvalue的概念。参考这里

元表

  • 若干table可以共用一个元表,用于共享一些自定义的行为,如两个表相加。

  • 一个表的元表可以设定为自己

  • __index元方法。当我们对一个表进行键值访问t.anonexsitkey,内部触发的逻辑顺序是:

    • 在表t中找anonexsitkey这个键,找到则返回对应值,找不到执行下一步

    • 找t的元表并判断元表有没有__index,找不到返回nil,有则执行下一步

    • __index若是函数则调用。其若是表就在这个表中找键值(这其实也可以理解为一个语法糖,因为用的太频繁)。

    • 注:不想触发__index则用rawget(t,key)

  • __newindex元方法。当我们对一个表的键赋值t.anonexsitkey = myValue,内部触发的逻辑顺序是:

    • 在表t中找anonexsitkey这个键,找到则替换成myValue,找不到执行下一步

    • 找t的元表并判断元表有没有__newindex,找不到则替换成myValue,有则执行下一步

    • __newindex若是函数则调用(myValue就不执行赋值),若是表则将myValue更新到这个表里而不是t中(有点像直接改变基类的成员)。

    • 注:不想触发__newindex则用rawset(t,key,value)

  • __metatable元方法。若mt是t的元表,则保护t的元表不被改变,可以 mt.__metatable = 'i am hiden and non-changable'。这样getmetatable(t)会给出这句话,setmetatable(t,other)会报错。参见

错误处理

  • assert( l1, msgStr) l1为false则打印msgStr

  • error(msgStr, level) level=0,1,2

  • pcall(func, param1, param2) 保护模式运行func,忽略func内部的错误处理,返回true或false

  • xpcall(func, errhandle, param1,param2) 和pcall类似,但自定义错误处理函数。结合:

垃圾回收

面向对象

  • lua没有class,而是类似prototype-based. 所谓prototype, 其实就是一个普通的实体,但只有一个作用:提供公共的行为。所以要切换一下思维:lua中的“类”不是一个抽象的概念,而是一个个活生生的原型实体。

下面的写法让b成为a的prototype(也可以称作b是一个class). a的metatable在这里是匿名的。

但这样每次以b为原型创建对象时都会新建一个metatable,挺浪费的。优化的写法是:

接着上面,如果b这张表里定义了一个键对应一个函数,比如 b:func

则现在a也可以调用

如果a也想作为一个类,并且继承b呢?下面将a打造成b的子类,改变func,创造出以a为原型的c

上述会输出defined by a而不是会输出defined by b,妙就妙在b:new中用了self。如果把self直接改写为b,那么由于b就是个实体,那么,b.new(a,nil)就还是会把c的metatable变成b,b.__index=b。

底层交互

  • lua和c存在两种调用关系

    • lua作为主程序,调用c编译出的库(例子,算法加速)

    • c作为主程序,调用lua编译出的库(例子,lua翻译器本身)

  • lua和c通信通过一个虚拟栈,来解决两种语言的差异:

    • GC。虚拟栈由lua维护,所以知道哪些正被C使用,故不清除。

    • 变量类型。主要是解决了若干种C类型和若干种Lua类型关联的组合爆炸。每种类型只需要实现和虚拟栈中统一类型的转化函数。

  • 虚拟栈有两种索引顺序:

    • 1.栈底(1)->栈顶(n)

    • 2.栈顶(-1)->栈底(-n)

    • C可以访问和修改栈任意位置的值,lua只能按照LIFO原则访问修改栈顶。

压栈和出栈取值

  • 压栈

    • lua->栈的lua_pushunumber

    • c->栈,userdata?稍后讨论

    • 一次压栈太多要防止超过容量。lua_checkstack

  • 取值

    • lua从栈上取值要判断类型,利用lua_isnumber之类的

    • c从栈上取值,利用lua_tonumber之类的,将返回double

    • 如果取的是table的某个键值,这里有一个特殊处理(假设lua中有一个bgcolor = {r=1, g=0, b = 0}):

  • 例子1

  • 例子2

    c调用lua的一个函数myluafunc

  • open lua state L (lua_open) 注:创建了一个全新的独立的lua环境

  • load lib into L (luaL_openlibs)

  • compile commandStr in buff , and push compiled code to L's stack - (luaL_loadbuffer)

  • pop code from L's stack and execute in protect mode (lua_pcall)

  • if lua throws error msg in L, print & pop it (lua_tostring, lua_pop)

  • close lua state L(lua_close)

C_API 错误处理

  • lua调用c库,

    c库会报错导致程序crash,标准的处理是让c库调用lua_error

  • c调用lua,

    分配栈空间不足也会报错导致crash(除非使用了保护模式即lua_pcall,其会返回错误码)

Last updated