首先介绍一下 lua 语言:

它是一门动态类型语言。所谓动态就是运行时,静态就是编译时。
静态语言会在编译时做类型检查,通过报错帮助程序员发现问题。
动态语言没有编译环节,类型检查发生在运行时,查到错误就会抛出异常,
程序员需要自己保证类型正确,或者做好异常处理。
因为没有类型的约束,lua 语言非常灵活,可以编写一些复杂的逻辑。
除了动态语言共同的优势以外,lua 自身有两大优势:

  • 尾调用,使得程序员可以放心的写递归算法,不必担心 StackOverflow
  • 标准化的 c api,使得 lua 语言可以与 c 语言互相调用,并且所写的 c 代码是跨平台的,不会因 lua 解释器不同而不同

一、类型系统分为“值类型”和“引用(对象)类型”

值类型有 5 种:

  1. nil类型,值也是nil
  2. boolean类型,值为truefalse
  3. number类型,默认由double类型实现。m^n,求mn次方
  4. string类型,'\0'字符不会截断字符串,可以用'"[[]]来构造字符串。
    #str,求 str 的字节数。str=str..str,连接两个字符串
  5. lightuserdata类型,对应 C 语言的指针值,是值不是对象

引用类型有 4 种:

  1. function类型,用function(参数列) end来构造这种对象
  2. table类型,用{}来构造这种对象
  3. thread类型,不是线程,是协程,不具有并发功能,只能用来分隔不相干的逻辑。
    每个thread有自己的call stack。由程序员调度所要执行的thread,其它thread会暂停
  4. userdata类型,Cocos2dx 里的 c++类的实例,在 lua 里就是这种类型。
    对比lightuserdata,一个是指针本身(32 位或 64 位无符号整数),一个是指针指向的对象

二、function 相关知识

  1. 传参和传返回值的时候,根据上一讲分的两大类,来传值或传引用。
  2. 除了传参和传返回值,function还会自动捕获外部的变量,术语叫upvalue,意思是上文中的变量。
    upvalue 一律传引用,也就是说,在函数内修改upvalue,那么函数外也能看到这一改变。
    利用自动捕获,function可以当做容器用,要访问容器中的变量,就必须调用该函数,以达到封装的目的
    lua 中很少做封装,常做的只有继承、多态。
  3. 尾调用。函数内出现return func(...),那么调用栈会先 pop 当前函数,然后把 func 压入调用栈。
    这一机制的好处是,可以无限制使用递归算法,而不会发生 stack overflow。
    坏处是,调用栈不完整,在栈上看到一个函数,难以推断是哪个函数调用了这一函数,
    因为上一个函数可能已经出栈了。写或不写 return,可以控制是否使用尾调用

三、table 相关知识

  1. table是哈希表,无序容器。当键是从 1 开始的连续整数时,可以当做数组来用(有序)
  2. 与函数自动捕获引用不同,table 可以存值进去
  3. 增、改:t[k]=v
    删:t[k]=nil
    查:if t[k]~= nil then ...
    无序遍历:for k,v in pairs(t) do ...
    有序遍历:for i,v in ipairs(t) do ...
  4. setmetatable(t,mt)用来给表 t 设置一个元表 mt,元表中存放的主要是元方法,
    比如__add, __sub, __mul, __div, __len, __concat, __gc
    前面几个相当于“运算符重载”,使 t 可以支持算术运算。gc 是垃圾回收的意思,相当于 t 的析构函数。
    可以看出,mt 控制着 t 的行为

四、类与类的实例

  1. 二者都是table,前者装有成员函数(共性),后者装有成员变量(个性)
  2. 语法糖 instance:method(argument)
    原型为 instance['method'](instance,argument)
    先在实例表里查找成员函数,通常表里只有成员变量没有成员函数,这种情况下会触发__index元方法,
    元方法去类表里继续查找,若依然找不到,再触发类表的__index元方法,一路找下去。
    找到之后,就把实例表、实参传入这个函数,于是函数内就能访问到成员变量了
  3. 语法糖instance.method(argument)
    原型为instance['method'](argument)
    这相当于其它语言中调用static成员函数,没有成员变量传入函数。
    或者class:method(argument)类表会被传入函数,
    static 成员变量应该存放在类表里,并且可以被 static 成员函数访问
  4. 上面的 class 指代一个类表,在 cocos 中,class 是一个全局函数,由 cocos 引擎提供。
    这个函数的作用是返回一个新的类表,并为其设置好:元方法__index(去父类中查找)、
    static 成员函数create(创建实例对象)、static 成员函数new(创建实例对象)、
    一般成员函数ctor(在实例对象中放入初始内容)。
    实例对象可能是tableuserdata,而类一定是table
    Cocos 引擎创建userdata的时候,总是同时创建一个table,并将二者绑在一起,
    所以可以对userdata使用table语法
  5. 上面说的语法糖,不止用于函数调用,也用于函数定义:
    local class = {}
    function class:ctor(sth)
    self.field = sth
    end
    --等价于:
    local class = {}
    function class.ctor(self, sth)
    self.field = sth
    end
    

其它

  1. lua 中任意类型的值都可以代表真假。只有nilfalse两个值代表假,其它值都代表真。
  2. andor运算符,不会把它们所连接的表达式转成boolean型,这一点跟其他语言不同。
    a and b or c与其它语言的 a ? b : c功能类似,具体是:若 a 和 b 为真,则返回 b;
    若 a 为假,则返回 c。若 a 为真,b 为假,则返回 c,这与?:不同。
  3. 参考手册 https://www.lua.org/manual/5.1/
  4. https://learnxinyminutes.com/docs/zh-cn/lua-cn/

乌托邦

xl

Stay hungry, Stay foolish.