Lua_isms

Posted 程序员不是码农

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Lua_isms相关的知识,希望对你有一定的参考价值。

《Programming in Lua的第三部分,主要介绍Lua中的迭代器,for循环,元表和元方法,面向对象编程,环境,垃圾回收,协程以及反射,这部分内容有点多,并且大多以代码进行讲解,这些知识点也Lua编程中非常重要的。


1
Iterators and the Generic for(迭代器与for)


Iterators and Closures(迭代器和闭包)

迭代器是一个允许我们遍历集合中所有元素的结构体,Lua中通常使用函数来表示迭代器,每次调用返回集合中的下一个元素,比如之前用到的io.read,每次调用都返回标准输入文件的下一行,如果没有数据可读了就返回nil。

任何迭代器都需要记住连续两次调用之间的状态,闭包提供了很好的机制来记录这些状态,实现这样一个机制需要两个函数:一个是闭包,另一个是工厂函数(用来创建闭包及其非静态变量)。以下为列表实现一个简单的迭代器:


接下来提供两种使用上述迭代器的方法:

Lua_isms

迭代器的实现可能会很复杂,但是对于使用者来说是很简单的,而大多数情况,我们不需要自己去实现一个迭代器,而只是去使用。


The Semanitics of the Generic for

上述迭代器(while循环)有一个缺点:每次开启一个新的循环都需要创建一个新的闭包。

for循环可自己保存迭代的状态来避免这种开销。其通常会保存三个值:迭代器函数,不变状态和一个控制变量。for的用法如下

Lua_isms


ipairs

ipairs使用和实现如下:


Lua_isms


当调用for循环时,其会获得3个值:迭代器函数iter,不变状态table t, 以及控制变量初始值0,接着Lua调用iter(t, 0), 其结果为1, t[1], 第二次迭代时,Lua调用iter(t, 1),其结果为2, t[2],直到遍历完所有元素。


pairs

pairs同ipairs类似,也是遍历table的所有元素,但是其用的迭代器是Lua原生的函数next,如下:


Lua_isms


Traversing Tables in Order(有序遍历Table)

考虑下面这个table,按照字母顺序打印。

Lua_isms


若使用pairs进行遍历,得到的结果是无序的,因为其使用next。因此需要用数组将table的key保存,对该table排序后再打印,如下:

Lua_isms


Exercise

Lua_isms



2
Metatables and Metamethods(元表和元方法)



当遇到未知操作时,元表允许我们改变值的行为,比如,让两个table相加a + b,会检查其中是否有元表,及其元表是否有__add字段,有的话,就会调用相应的元方法。


table默认是没有元表的,可以通过setmetatable来设置一个table的元表,如下:

Lua_isms


字符串库给字符串默认设置了元表,其他类型的值是没有元表的

Lua_isms


Arithmetic Metamethods(算数元方法)

用table来表示集合,并实现并集、交集等方法,如下:

Lua_isms


接下来用元表来实现通过+号来求得其并集,在创建set的时候,需要设置其元表:

Lua_isms


Relation Metamethods(关系元方法)

元表允许我们通过元方法(__eq, __lt, __le)赋予关系操作符意义。接下来通过元方法判断两个集合的关系:

Lua_isms


The __index metamethod

之前提到,当我们访问table中不存在的字段时,其结果会是nil,但不全对,实际上,解释器会去寻找元表中是否有__index元方法,如果没有当然返回nil,如果有的话,相应的元方法会提供结果。


接下来以窗口为例来说明__index的使用:

Lua_isms


The __newindex metamethod

当把一个值赋给table中不存在的key时,解释器会寻找__newindex对应的元方法,把赋值操作转换为调用__newindex对应的元方法。


Lua中提供了 rawget rawset 两种方法,来 禁止触发 __index和__newindex的调用。


用元表来实现带有默认值的table,如下:

Lua_isms


上述这种设置默认值的方法中,如果d中没有对应字段,会继续触发d的元表,效率很低,可以把默认值直接保存在表的字段中,如下:

Lua_isms


使用元表来实现跟踪table的访问:

Lua_isms


使用元表实现只读table:

Lua_isms


3
Object-Oriented Programming(面向对象编程)



Class(类)

Lua中没有类的概念,但可以通过元表来模拟出类的实现。可通过__index元方法和继承的概念来实现原型,以下为一个Account类:

Lua_isms

上述做法可以改进,其实不必要创建mt,直接将self设置为其元表即可,如下:

Lua_isms


Inheritance(继承)

因为类是对象,它能从其他类中获得方法,这种行为使得在Lua中实现继承变得非常简单。假设有一个基类Account如下:

Lua_isms

创建一个Account的实例SpecialAccount:SpecialAccount = Account:new(), 接下来调用SpecialAccount的实例s:s = SpecialAccount:new{limit=1000.00},。

这三者关系可以理解为:s继承SpecialAccount,SpecialAccount继承Account, 如果调用s的某个方法,如果s中找不到就会去SpecialAccount中搜索,SpecialAccount中找不到就去Account中找。也可重定义Account中的方法:

Lua_isms


Multiple Inheritance(多重继承)

一个多重继承的实现如下:

Lua_isms


接下来介绍如何使用多重继承,先定义一个类Name如下

Lua_isms


创建一个新的类NameAccount,继承Account和Named如下:

Lua_isms


Exercise

Lua_isms


4
The Environment(环境)



Lua把自己的全局环境存储在全局变量_G中,并且_G._G==_G

getfieldsetfield实现方式如下:

Lua_isms


Global_Variable Declarations

使用元表来检查访问的变量是否存在或被申明,如下:

Lua_isms


Non-Global Environments and _ENV

Lua中全局变量没有必要真的是全局的,其实Lua压根就没有全局变量。把没有申明为local的变量定义为free变量,Lua把代码块中所有free变量放在_ENV表中,如下:


Lua_isms


_ENV并不是一个全局变量,Lua中所有的代码块都可视为匿名函数,而这个_ENV就是这个匿名函数的上值(up-value),当我们load一个Lua代码块(chunk)时,会使用全局环境来初始化_ENV表,如下:


Lua_isms


对Lua中全局变量的处理总结如下:

  1. 编译时会在代码块匿名函数外创建一个局部变量_ENV

  2. 编译器会把所有的free变量放入_ENV

  3. 函数load or loadfile用全局环境来初始化代码块的第一个上值(up-value)


Lua交互模式下,每一行都有一个不同的_ENV,可以使用do .. end来运行一个代码块(沙盒环境),可以通过让_ENV = nil来使得代码块中之后对全局变量的访问都失效,如下:

Lua_isms


_G_ENV虽然指向相同的table,但是他们是不同的实体,_ENV是一个局部变量,代表的是当前的环境,代码块中所有对“全局”变量的访问都是访问_ENV_G代表的是全局的环境。可以随时改变_ENV,如下:

Lua_isms


Environments and Modules

避免污染全局变量的模块实现:

Lua_isms


Exercise

Lua_isms


5
Garbage(垃圾回收)


Lua中主要的垃圾回收机制:weak table, finalizers, 和函数collectgarbage


Weak Tables(弱表)

垃圾回收器无法判断那些我们认为的垃圾,比如stack,通过一个数组和一个top索引值来实现,当pop元素时,只减少top的索引值,则栈顶元素还在数组中,垃圾回收器不会认为这是垃圾,而在我们看来它就是垃圾。因此,需要我们将其置为nil,才能被回收。


Weak tables其实就是告诉Lua这个引用的回收不受保护。因此如果所有对这个对象的引用都是弱引用,垃圾回收器会回收这个对象并删掉所有的弱引用。Lua通过weak table来实现弱引用

有三种类型的弱表: 弱键(weak key), 弱值(weak value), 弱键和弱值(both keys and values are weak),通过元表的__mode字段来设置弱表的类型,三种情况分别对应k, v, kv。

Lua_isms

不管什么类型,当key或者value被回收后,其对应的entry都会从table中删掉。


meorization机制:load是一个很耗的操作,因此可以把其结果记录下来,每次load前会先判断是否在记录中,如果在,则直接使用,不需要再load,如果不在,则调用load并把结果记录。


但是这种机制有一个缺点,一旦记录下load的结果,就会一直占用内存,哪怕之后不再用到,久而久之,必然耗尽内存。因此可以有弱表来优化这种机制,对于一些不再用的,直接交给垃圾回收机制处理,一个color的例子如下:

Lua_isms


Finalizers

finalizer是一个跟对象相关的函数,当对象将要被回收时被调用。Lua通过元方法__gc来实现finalizers,如下:

Lua_isms


注意__gc添加必须在setmetatable之前,如下:

Lua_isms


The Garbage Collector

标记清除分为四步:mark, cleaning, sweep, finalization.

mark: 从根节点开始,标记Lua能够访问到的节点为alive

cleaning: 处理finalizers和弱表,遍历所有finalization的对象中没有被标记为alive的对象,放入另一个列表中,用于finalization阶段。然后遍历弱表,将其中key或者value没有被标记为alive的entry从table中移除。

sweep: 遍历所有的Lua对象,回收没有被标记为alive的对象

finalization: 针对cleaning阶段放入列中的对象,调用其__gc元方法。


Lua 5.0在gc时需要暂停主程序,Lua5.1推出了一种增量gc的方法,不需要停止正在运行的程序,一步一步来来gc。


6
Coroutines(协程)



协程跟线程类似,它是一条执行的流程,有自己的栈、局部变量,指令指针,并且和其它线程共享全局变量。协程和线程的主要区别在于多线程程序是可以并行运行的,而协程同时只能有一个在运行,其它的挂起,因此协程之间是协作关系(collaborative)。


Coroutine Basics

Lua把所有协程相关的函数放在coroutines表中,create创建新协程,传入协程执行的方法,返回一个线程来代表新的协程,如下:

Lua_isms

协程有四种状态:挂起(suspended),运行(running),正常(normal),死亡(dead),可以使用coroutine.status来查看但协程的状态。协程创建的初始状态为挂起,通过coroutine.resume来启动协程,将其状态改为运行状态。正在运行的协程可以通过coroutine.yield来交出执行权,并切换至挂起状态,等待下一次resume。

通常可以通过resume-yield对来交换数据,如下:

Lua_isms

协程一个典型的例子就是生产者-消费者问题,如下:

Lua_isms


Event-Driven Programming

Lua_isms


Exercise


7
Reflection(反射)



反射机制:程序具有检查和修改其执行流程的能力。动态语言天然支持一些反射特性:环境允许在运行时inspect全局变量,typepairs函数允许在运行时inspect和遍历不了解的数据结构等。但是仍然有些没法做到:程序不能inspect其局部变量,不能跟踪函数的执行,函数没法知道它的调用者等等,这些在debug库得以实现。

debug库由两种类型函数组成:introspective functionshhooks,前者允许我们可以检查正在运行程序的一些状态:函数栈,当前执行哪一行,局部变量的值等等。后者允许我们跟踪一个程序的执行流程。


主要的introspective functiongetinfo,其第一个参数可为一个函数或者stack level, 返回这个函数的一些数据。


通过debug.getlocal访问局部变量:,第一个参数为stack level, 第二个参数为变量索引,返回改变量的当前名称和值。


以上通过大量实例,介绍了Lua中的迭代器,for循环,元表和元方法,面向对象编程,环境,垃圾回收,协程以及反射。


参考:

http://www.lua.org/pil/

http://www.lua.org/manual/5.3/manual.html#lua_call

END



以上是关于Lua_isms的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段——CSS选择器

谷歌浏览器调试jsp 引入代码片段,如何调试代码片段中的js

片段和活动之间的核心区别是啥?哪些代码可以写成片段?

VSCode自定义代码片段——.vue文件的模板

VSCode自定义代码片段6——CSS选择器

VSCode自定义代码片段——声明函数