关于 Lua 的那些问题(长期更新)

0x00 〇

关于,我,为什么这么久,没发文章。那还要从 1 月 2 号说起,想我们在 2 号放完仅仅一天的元旦假期之后,回到工位,愕然发现:工作室没了。

然后就,这么久了,还没缓过来。

0x01 一 问题目录

基础

数值

字符串

函数

输入输出

补充

闭包

模式匹配

位和字节

数据结构

数据文件和序列化

编译、执行和错误

模块和包

迭代器和泛型 for

元表

面向对象编程

环境

垃圾收集

协程

反射

0x02 二 问题解答

Q1

问题:什么叫动态类型语言(Dynamically-typed language)?

动态类型语言,是指在语言中没有类型的定义,不需要声明变量的类型,类型信息由变量自己保存,或者说是由每个值自带其类型信息。Lua 中的基本类型有 8 种:nil(空),boolean(布尔),number(数值),string(字符串),userdata(用户数据),function(函数),thread(线程),table(表)。

Q2

问题:全局变量的默认值是什么?如何删除全局变量?

在 Lua 中,全局变量无须声明即可使用,默认值为 nil,即表示未经初始化的变量。若要删除一个变量,只需要将变量值置为 nil 即可。Lua 不会区分未初始化的 nil 和被赋值为 nil 的变量,并且 Lua 语言会自动回收变量占用的内存。

Q3

问题:Lua 中的条件测试(以及逻辑运算符)将哪些值视为假?

在 Lua 语言中,条件测试只将 false 和 nil 视为假。有些语言中视为假的“0”在 Lua 中也同样为真。

Q4

问题:什么是短路求值原则?

短路求值是指只在必要时才对后面的操作数进行求值。例如“true or something”,无论 something 逻辑运算的结果为什么,都不会改变整个“or”运算的结果,因此 something 中的语句都不会被执行,被短路处理。

Q5

问题:Lua 逻辑运算(and,or,not)返回的运算结果分别是什么?

逻辑运算符 and 和 or 为二目运算符,not 为一目运算符。运算符 and 在第一个操作数为“false”时返回第一个操作数,否则返回第二个操作数;运算符 or 在第一个操作数不为“false”时返回第一个操作数,否则返回第二个操作数;运算符 not 永远返回 boolean 类型的值。

Q6

问题:Lua 的独立解析器有哪些特点?

Lua 的独立解析器的源文件是“lua.c”,是一个可以直接使用 Lua 语言的 C 程序。在使用 Lua 时,可以使用“#”作为一个 Lua 文件的开头,让系统直接使用独立解释器运行 Lua 脚本。如:“#!/usr/local/bin/lua”,可以通过可选参数运行脚本,通过名为“arg”的表为脚本传递参数。

Q7

问题:阶乘的示例程序如果输入负数,会出现什么问题?如何修改。

1

这个示例中,没有考虑到输入为负数的情况,程序会一直执行下去直到报错“stack overflow”,需要修改程序的停止条件。

Q8

问题:解决八皇后问题。

使用回溯法的经典问题。主要思路是查看每行每一个位置的合法性,当一个位置不合法时,返回上一行的下一个位置,继续检查合法性。当每一行都放入了皇后,即得到了一个解。

解决方案中使用了对每一列的循环,检查每一个位置,在找到一个解后不会停止循环,而是继续检查一下位置。而保存解的表并非保存有所有解,而是将解打印出来前的缓存。

Q9

问题:找出皇后问题的所有不重复的解。

如何看“重复”这件事呢。

Q10

问题:如何生成一个数列的排列?

利用回溯,将未排列的数字插入到下一位置。

Q11

问题:用生成排列的方式获得八皇后问题的解,与示例的性能差异。

使用回溯的方法,比较了17777次,而先生成排列的方法比较了140756次,多了一个数量级。原因在于,回溯的方法并不会对开头部分有重复的错误解进行多次的比较,而全排列的方法,将开头有重复的错误解也作为不同的解来处理,所以多做了很多工作。

Q12

问题:双精度浮点数的误差是怎么来的?

用十进制表示的小数和用二进制表示的小数可能不同,如12.7 - 20 +7.3 在计算后不为0。
而小于2^53(即9007199254740992)的整型值与双精度表示的浮点值是一样的。

Q13

问题:如何保留所有浮点数的精度?

使用十六进制浮点数来表示浮点数,可以保留所有浮点数的精度,并且比十进制的转换速度更快。

Q14

问题:为什么要少用字符串连接操作符(..)?

在Lua中,字符串时不可变量,使用连接操作符时总是会创建一个新字符串,而不会改变原来作为操作数的字符串。

类似C#中,使用“+”也是同样的道理。C#中可以使用StringBuilder类型的变量来进行拼接。

Q15

问题:什么是“一组包含歧义的转义字符序列”,可以用来做什么?

Q16

问题:处理字符串和utf-8字符串的区别在哪里,需要注意些什么?

Q17

问题:Lua中表的本质是什么?

Lua中的表本质上是一种辅助数组(associative array),不仅可以使用数值作为索引(Lua中索引默认从1开始,但也可以使用0或负数),也可以使用字符串或其他类型的值作为索引(nil除外)。

Lua中的表是一种动态分配的对象,程序只能操作指向表的引用,Lua语言不会对表进行深拷贝(拷贝的是对象的引用,而非整个对象本身)。

Q18

问题:构造表的几种方法?

使用表构造器(Table Constructor,空构造器{},或使用索引从1开始的构造器{…}),即列表式(list-style);
使用初始化记录式(record-style)表的语法,如{a=1, b=2};
两种风格可以在同一个表的构造时混用。

若要使用负数索引,可以使用方括号显示指定索引,如{[-1]}

Q19

问题:序列(sequence)在Lua语言中是什么?

序列在Lua语言中指的是由指定的n个正整数数值类型的键所组成的集合{1,2,3…,n}组成的表,不包含空洞(nil)。Lua中获取长度的操作符返回的就是表对应序列的长度。

Q20

问题:如何遍历表?

可以使用pairs迭代器遍历表,但由于底层实现机制,这种遍历只能保证每个元素会且只会出现一次,但遍历过程中元素的出现顺序可能是随机的,每次运行也可能产生不同的顺序。

如果是列表(序列),可以使用ipairs迭代器进行遍历,此时会确保是按照顺序进行的。另外,对于序列也可以使用for循环遍历。

Q21

问题:当进行字符串拼接时,在Lua语言中性能比较好的方法是?

使用table标准库中的table.concat方法对表内的string数据进行拼接。测试1,000个0到1之间的浮点数字符串拼接,“..”操作符使用了0.751秒,table.concat方法使用了0.001秒;10,0000个0到1之间的浮点数字符串的拼接,使用“..”操作符使用了73.426秒,而使用table.concat方法,只用了0.017秒。以上。

Q22

问题:当什么时候Lua中函数的参数可以不带括号?

当函数只有一个参数,且参数为字符串常量或表构造器时,可以省略括号。

Q23

问题:什么是尾调用(tail call)?

尾调用是被装饰为函数调用的“goto”语句,当一个函数最后一个动作是调用了另外一个函数而没有进行其他工作时,就形成了尾调用。当使用尾调用时,由于不需要进行其他工作,程序不需要返回最初的调用者,因此不需要在调用栈中保存调用函数的信息。如f调用g,当g返回时,会直接返回到调用f的位置,即Lua语言支持尾调用消除(tail-call elimination),使得在进行尾调用时,不需要任何额外的栈空间(永远不会发生栈溢出)。

Q24

问题:洗牌算法有哪些?

洗牌算法(Shuffling Algorithm)是用来产生一串等概率随机序列的算法。

Q25

问题:I/O操作都包含哪些?

Q26

问题:什么是简单I/O模型?什么是完整I/O模型?

简单I/O模型是虚拟了当前输入流和当前输出流(current input stream,current output stream),并初始化为标准输入和标准输出(io.input和io.output可用来改变当前的输入输出流),而I/O操作是通过这些流实现的。

要同时处理多个文件,需要完整I/O模型。即通过io.open函数建立了不同文件的流,再使用read和write方法进行操作。而且标准输入输出是以文本模式进行的,无法处理二进制流。

Q27

问题:io.write方法与print方法有哪些不同?

函数io.write不会在输出的结果中添加制表符或换行符;函数io.write允许对输出进行重定向,print只能使用标准输出;print函数会对参数自动调用tostring。

Q28

问题:什么是数据驱动编程?

数据驱动编程的出发点是相比程序逻辑,人类更擅长处理数据。应将设计的复杂性从程序代码转移至数据端。如消息机制从switch分支,变为自定义的包含了消息处理函数指针的数据结构,https://www.cnblogs.com/chgaowei/archive/2011/08/03/2126724.html。

优势:可读性强,代码流程一目了然;(机制和策略分离)
易于修改,对于新功能的增加只需要增加数据,而不需要修改流程;(隔离变化)
重用了重复代码。

参考《Unix编程艺术》

Q29

问题:第一类值(first-class value)是什么?

第一类值(也称为first-class object, first-class entity)来源于1960年代,原称为第一类公民(first-class citizen)意指函数可作为电脑语言中的第一类公民。第一类值有以下特征:可以存入变量或其他结构;可以作为参数传递给函数;可以作为函数的返回值;可以在执行期创造而非完全在设计期全部写出;可以是匿名的。

在Lua语言中,函数是第一类值。可以以另一个函数为参数的函数,被称为高阶函数(higher-order function)。

Q30

问题:词法定界(lexical scoping)是什么?

定界(scope)是指变量域变量所对应实体之间绑定关系的有效范围。有时也可以与可见性(visibility)混用。

也被称为静态定界(静态域)(static scoping),指变量只能在其被定义的作用域范围内被使用。与之相对的是动态定界(dynamic scoping),动态定界常见于shell,bash语言。

对于静态定界,一个变量的可见性范围仅严格与组成程序的静态具体词法上下文有关,而与运行时的具体堆栈调用无关;使用动态定界时,一个变量的可见性范围在编译时是无法确定的,而依赖于运行时的实际堆栈调用情况。

https://en.wikipedia.org/wiki/Scope_(computer_science)

通俗的讲:词法定界可以当作一种特性,即当在一个函数B中编写一个函数A时(当编写一个被其他函数B包含的函数A时),被包含的函数A可以访问包含它的函数B中的所有局部变量。

Q31

问题:为何需要在定义局部递归函数前,先定义局部函数变量。

由于使用匿名函数定义后赋值给代表函数名的变量中,而在匿名函数的定义中使用了即将赋值的变量名,(构成局部递归,recursive local function),而此时,局部的变量仍未被定义。因此需要提前定义保存匿名函数的变量。

而使用函数定义的语法糖不会有这个问题,是因为:

1
2
local function foo(params) body end
local foo; foo = function(params) body end

两者等价。前者将会被展开为后者。

Q32

问题:什么是闭包?

一个闭包由一个函数和能够使这个函数正确访问非局部变量(即不是上层函数的局部变量,也不是全局,如上层函数的形参,也被称为上值)的机制组成。当一个函数返回的是匿名函数时,每次调用这个函数,都会返回一个新的闭包。

因此,Lua从技术上讲只有闭包没有函数,函数只是闭包的一种原型。

Q33

问题:典型的正则表达式实现。

典型的POSIX正则表达式实现需要4000行代码。

Q34

问题:字符串标准库中基于模式(pattern)的函数有哪些?

函数find,gsub,match,gmatch。

函数find返回匹配位置,用sub来获取匹配到的字符串。
函数gsub用来替换。
函数match直接返回匹配的子串。
函数gmatch会返回一个可以用来遍历所有匹配指定模式的字符串。

Q35

问题:Lua的模式匹配有什么特点?

在Lua语言中,不使用大多数匹配库使用的反斜杠(backslash)作为转义符。

对于Lua语言来说,模式仅仅是普通的字符串,只有与模式匹配相关的函数才会将这些字符串作为模式进行解析。Lua中的模式使用“%”作为转义符。

具有特殊含义的字符有:

1
2
3
4
5
6
7
%a %c %d %g %l %p %s %u %w %x
%A %C %D %G %L %P %S %U %W %X
( ) . % + - * ? [ ] ^ $
%( %) %. %% %+ %- %* %? %[ %] %^ %$
%b
%f
%n %2 % 1 %0

Q36

问题:什么是算术位移,什么是逻辑位移?

逻辑位移(logic shift)是在移位后,用0补充空位。
算术位移(arithmetic shift)时,左移与逻辑左移相同,会用0补充右边的空位;而右移时,算术位移会用符号位补充空位。

Q37

问题:大端模式,小端模式?

大端模式(big-endian):是指数据的高字节保存在内存的低地址中,低字节保存在内存的高地址中。
小端模式(little-endian):是指数据的高字节保存在内存的高地址中。

常见的如x86结构,很多ARM(有些可以进行设置)及DSP是小端模式,Keil C51为大端模式。

Q38

问题:如何将windows格式的文本文件转换为POSIX格式?

将\r\n转换为\n。

Q39

问题:Lua中的数据结构。

数组,矩阵(多维数组,稀疏矩阵,邻接矩阵),链表(单链表,双向链表,环形表),队列,双端队列,反向表,索引表,集合,包(多重集合),图。

Q40

问题:如何使用Lua描述数据?

将数据文件写成Lua代码形式(表构造器作为参数,保存有数据,并统一使用预定义的函数进行读取),并在沙盒中执行预定义的函数进行读取。

Q41

问题:何为序列化?

序列化(Serialization)是将数据转换为字节流或字符流,以便将其存储到文件中或通过网络传输。

Q42

问题:如何保存带有循环和共享子表的表?

对于要保存的值,使用type函数进行区分,将非表的值使用%q格式化为字符串写入流,对于表,则首先保存表结构,随后保存每一个k-v值。对于带有循环和共享子表的表,可以引入名称作为参数,将已经保存的表进行记录,当有值为已经保存的表时,直接引用即可。如果要保存的几个表有共享内容,可以使用同一个记录集合进行保存。

Q43

问题:什么是解释型语言(interpreted language)?

相对于编译型语言存在,源代码不直接翻译成机器语言,而是翻译成中间代码进行解释运行。Lua作为解释型语言会在运行代码前预先编译(precompile)源码为中间代码(.lc),并可以轻易执行动态生成的代码(dofile,load,但这些函数会再调用时进行独立的编译,影响性能)。

Q44

问题:什么Lua中的模块(module)以及包(package)?

包是一个树状结构的模块的集合。
模块是一些代码的集合,主要目标是允许共享代码,可以使用函数require来加载模块,得到一个对应模块的表(所以也是第一类值)。

函数require加载的模块只会在第一次执行,并将模块名保存在package.loaded表中,同名直接返回表中的值。

Lua模块支持带有层次结构的模块名,使用点分隔名称中的层次。

Q45

问题:迭代器(iterator)是什么?

迭代器是一种遍历一个集合中所有元素的代码结构(wikipedia中,称其为程序设计的软件设计模式,是可在容器对象上遍访的接口)。

Lua中的迭代器通常用函数表示,每次调用函数(迭代器)时,都会返回集合中的“下一个”元素。迭代器通过闭包来保存(调用之间的)状态。创建闭包及封装非局部变量的函数成为工厂(factory)。

Q46

问题:泛型for的执行原理,过程?

泛型for首先对in后面的表达式求值,将返回的3个值保存在变量列表中的对应变量中(多丢少补nil):迭代函数、不可变状态、控制变量的初始值。随后for使用不可变状态及控制变量作为参数调用迭代器函数,并将返回值继续赋给变量列表中声明的变量。当第一个返回值(控制变量)为nil,则循环终止。

Q47

问题:什么是无状态迭代器(stateless iterator),有什么优势?

无状态迭代器是本身不保存任何状态的迭代器,只根据传入的不可变状态与控制变量来得到迭代的下一个元素(如ipairs工厂及迭代器)。
可以避免在每次循环时创建新的闭包,从而避免了开销。

Q48

问题:实现马尔可夫链算法?

Q49

问题:元表是什么,有什么作用?

元表可以修改一个值在面对未知操作时的行为。元表可以认为是面向对象领域中的受限制类。Lua中每一个值都可以有元表。表和用户数据类型都有各自独立的元表,其他类型的值共享对应类型所属的同一个元表。在Lua中只能为表设置并修改元表,其他值必须通过C代码或调试库完成。只有所有字符串值都默认设置了同一个元表,其他值默认没有元表。

Q50

问题:元表有哪些元方法?

算术运算符对应的元方法有:

元方法 算术运算符 备注
__add 加法操作,+
__mul 乘法操作,*
__sub 减法操作,-
__div 除法操作,/
__idiv floor除法操作,//
__unm 负数,- 一元
__mod 取模,%
__pow 幂运算,^
__band 按位与,&
__bor 按位或,
__bxor 按位异或,~
__bnot 按位取反,~ 一元
__shl 按位逻辑左移,<<
__shr 按位逻辑右移,>>
__concat 字符串连接,..

关系运算元方法有:

元方法 关系运算符 备注
__eq 等于,==
__lt 小于,<
__le 小于等于,<=

库定义相关的元方法有:

元方法 功能 备注
__tostring 格式化输出的tostring函数 会在print时自动调用
__metatable 保护元表,get返回此字段,set报错
__pairs 函数pairs,进行遍历

表相关元方法有:

元方法 功能 备注
__index 访问表中不存在的字段时,会查找此元方法 表(在表中查找字段)或函数(执行此函数),函数的开销更大但更灵活
__newindex 当对表中不存在的索引赋值时,会查找此方法 表(在表中进行赋值)或函数(执行此函数)
__len 长度操作符,#

Q51

问题:在进行关系运算时,什么叫做部分有序(partial order)?

部分有序是指,并非所有类型的元素都能被正确的排序。NaN(Not a Number)存在,表示未定义的值,如0/0的结果,标准规定下,任何涉及NaN的比较都为假,因此在大多数计算机中浮点数就是部分有序的。

Q52

问题:如何实现具有默认值的表(默认值不为nil)?

基本思路是在访问一个表时,如果对应字段不存在,则访问__index函数,返回默认值。

可以避免每一种默认值都创建新的元表和闭包,可以使用默认键或创建排除表作为键,也可以使用对偶表(dual representation)(独立的表,表的键为各种表,值为这些表的默认值),需要使用弱引用表(weak table),便于垃圾收集。

Q53

问题:如何跟踪对表的访问?

基本思路是对每一次访问都通过__index以及__newindex元方法提供的函数进行,因此要保持进行跟踪的表为空,才能保证所有对表的访问都是通过元方法进行的。所以建立一个对要监控的表的一个代理(proxy),来实现监控。

在监控多个表时,同样可以将代理和原来的表映射起来,避免为每一个表创建不同的元表。

Q54

问题:调用元方法时,参数是如何传递的?

Q55

问题:如何实现只读表?

Q56

问题:表与对象。

一个表就可以看作是一个对象:表与对象一样,可以拥有状态;表与对象一样,拥有一个与其值无关的标识(self)(一般用作方法的接受者,receiver,如this指针);具有相同值的对象(表)是两个不同的对象,一个对象可以拥有不同的值;表与对象都拥有与创建者和被创建位置无关的生命周期;表与对象一样可以拥有自己的操作(方法,method)。

Q57

问题:Lua的面向对象有什么特点?

C++/C#等的类是一种模板,而对象是某个特定类的实例(instance)。而Lua中没有类的概念,虽然元表在一定程度上与类的概念类似,但仍无法将元表当作类使用。参考JavaScript等语言的基于原型的语言(prototype-based language)的做法,对象不属于类,每个对象都有一个原型(prototype)而原型也是一个普通的对象。当一个对象(类的实例)遇到一个未知操作时,会首先在原型中查找,因此,创建一个专门用作其他对象(类的实例)的原型对象来表示一个类。(类与原型都是一种组织多个对象间共享行为的方式。)

setmetatable(A, {__index = B}) – B是A的一个原型。

Q58

问题:使用__index元方法作为面向对象的实现,单继承与多重继承有哪些区别?

Lua中的面向对象使用基于原型的实现方式,这种方式下,类和对象的关系不同于C++等语言,类不是作为模板而是一个普通的对象。因此在这种情况下,单继承的类与对象(类的实例)的关系与子类与父类的关系相同(对象的元表为类,子类的元表为父类)。而在多重继承时,类与对象的关系,与类与超类的关系不同(类的元表是超类的集合,需要单独创建,__index元方法为函数;而对象的元表依然为类)。

Q59

问题:如何实现类的私有性(Privacy)?

又称为信息隐藏(information hiding),通常对于不想被外部访问的私有名称,通常的做法是在名称后添加一个下划线,但不会阻止外部对其的访问。
通过Lua的元机制(meta-mechanism)可以模拟私有性,通过两个表来表示一个对象,一个表保存对象的状态,另一个表保存对象的操作(接口)。表示对象状态的表不保存在其他字段中,而是只保存在方法的闭包中。

另外还可使用单方法对象,通过返回唯一的入口函数,通过闭包来保存一个对象,入口函数根据参数进行分发。

通过对偶表示来实现私有性,对偶表示是将一个表(永远不会被垃圾收集,可使用弱引用表)作为键,同时有把对象本身当作这个表的值。

Q60

问题:通过代理表示对象的对偶表示有什么优点和缺点?

Q61

问题:Lua语言是如何模拟全局变量的?

Lua将所有的全局变量保存在名为全局环境(global environment)的普通表(全局变量_G)中。

Q62

问题:Lua中的_ENV和_G有什么区别?

_G是保存了全局环境的全局变量。在Lua中,没有关联到显式声明上的名称(即不出现在对应局部变量的范围内)叫做自由名称(free name)。_ENV是编译时预定义上值(外部的局部变量)。_ENV是一个局部变量,所有在代码段中对“全局变量”的访问实际上都是对_ENV的访问。

Q63

问题:Lua语言的内存管理(垃圾收集)机制?

Lua中使用自动内存管理,程序可以创建对象,但删除对象是通过垃圾收集(garbage collection)自动进行的。Lua中辅助垃圾收集的机制主要有弱引用表(weak table)、析构器(finalizer)和函数collectgarbage。

弱引用表允许收集Lua语言中还可以被程序访问的对象;析构器允许收集不在垃圾收集器直接控制下的外部对象;函数collectgarbage允许控制垃圾收集器的步长。

Q64

问题:什么是强/弱引用?

垃圾收集器不会收回一个在可访问的表中作为键或值的对象(表或闭包),通常一个表中对于键和值的引用都是强引用,会阻止其指向的对象被回收。而弱引用则是不在垃圾收集器考虑范围内的对象引用。弱引用表有三种类型:具有弱引用键的表(__mode = “k”)、具有弱引用值的表(__mode = “v”)、具有弱引用键和值的表(__mode = “kv”)。常用在对偶表示中。

Q65

问题:啥是瞬表(ephemeron table,直译蜉蝣表)?

瞬表是具有弱引用键和强引用值的表。为解决一个具有弱引用键的表中又引用了对应的键作为值。瞬表的意义在于,表中的值只能通过弱引用的键来访问,即键的访问性控制了值的访问性。在例子中,通过表作为键,获得一个返回此表的闭包,虽然在闭包中保存了表的引用,但若其他外部引用不存在,gc仍然会收集作为弱引用键的对象,并从瞬表中移除。

Q66

问题:Lua中的对象析构器进行析构后为什么还可以访问?

出现这种情况一般是在两次垃圾收集之间,对象
被析构器标记,但还没有被回收。Lua中的要进行析构的对象,设置的元表时要存在析构器(设置元表后为元表添加析构器是不行的)是对对象进行标记,当标记后,一旦该对象不可达,垃圾收集器就会在下次垃圾收集时将该对象放入析构队列中进行析构,而正在被析构的对象会临时复苏(transient resurrection),并将该对象标记为已被析构;当下一次垃圾收集器发现该对象不可达时,就会将该对象删除。

Q67

问题:进程,线程和协程?

对于操作系统来说,线程是最小的执行单元,进程是最小的资源管理单元。进程是一个应用程序的启动实例,拥有代码和打开的文件资源、数据资源、独立的内存空间。线程从属于进程,是程序的实际执行者,有自己的栈空间,多个线程可以同时执行(并行性)。

协程与线程类似也有自己的栈、局部变量、指令指针,并与其他线程共享全局变量和资源。协程需要彼此协作地运行(并发性),同一时刻只能有一个协程在运行,并且只有当正在运行的协程显式地要求挂起(suspend)时才暂停执行。生产者-消费者模型使用多线程实现,性能影响较大,使用轻量级的协程则很好的解决性能问题。

协程不在操作系统管理,是在用户态执行,可以完全被程序所控制,因此切换协程不会消耗太多的性能。

Q68

问题:Lua语言协程的状态?

挂起(suspended)、运行(running)、正常(normal)、死亡(dead)。
挂起是协程暂停运行,当一个协程被创建时,即为挂起状态,不会自动执行。运行为正在执行的状态。正常状态是一种较为特殊的状态,当协程A唤醒协程B时,协程B从挂起转为运行,而协程A此时非运行也非挂起,此时的状态被称为正常。死亡是执行完毕。

Q69

问题:Lua中如何使用协程模拟事件?实现事件驱动编程。

Q70

问题:什么是反射(Reflection)?

诸如Java,C#和Lua语言都有反射的特性。总的来说,反射指的是程序在运行时(runtime)可以访问、检查和修改自身某些部分(状态或者行为)的能力。

在概念上,反射通常与“内省(type introspection)”的概念进行区别。内省(或者自省,introspective facility)机制是指程序在运行时对自身信息(元数据)的检测。

在Lua中主要支持以下几种反射机制:环境允许运行时观察全局变量;type以及pairs函数可以运行时检查和遍历未知数据结构;load和require函数可以在程序中追加和更新代码;通过调试库中的自省函数(introspective function)和钩子(hook),对活动函数的栈、正在执行的代码行、局部变量进行检查,以及跟踪程序的执行。

Q71

问题:什么是钩子(Hook)?

调试库中的钩子是一种可以由用户注册在程序运行时的某特定事件发生时调用的钩子函数的机制。可以触发钩子的事件有:调用一个函数时产生call事件;函数返回时产生return事件;开始执行一行新代码时产生line事件;执行完指定数量的指令后产生count事件。