2019年8月刚入职新公司时,因为之前的项目都没有使用lua的经验,所以jojo老大出了一份题让我想尽办法找出答案,当时对于一个无经验的小菜鸟来说,属实费了不少功夫,如今分享出来,希望能对刚使用lua的朋友们有所帮助,如果有大佬看到有错误的地方,欢迎指出,感激不尽。

1、Lua的基础工作原理,.lua文件实时编译之后,给到虚拟机的是什么指令.

具体指令形式有看吗?这个指令占了多少位数据,第n位主句代表啥,稍微看一下,有一个认识。 然后这些指令,具体怎么跟lua源码的模块代码相结合呢?比如我们是怎么调用到Talbe里面的add的? 其实每个指令具体执行,都有一个switch(指令类型)这样执行的,找到这个文件,然后有时间可以大概了解一下lua的文件结构,大概每个文件都放了一些啥,可以更深入了解一下。 lua源码(window项目)可以打开tolua_rumtime-master_5_3_2lua-5.3.3lua.sln来看

  • Lua使用虚拟堆栈向C传递值。此堆栈中的每个元素表示Lua值(nil,number,string等)。API中的函数可以通过它们接收的Lua状态参数访问此堆栈。
  • Lua运行代码时,首先把代码编译成虚拟机的指令("opcode"),然后执行它们。 Lua编译器为每个函数创建一个原型(prototype),这个原型包含函数执行的一组指令和函数所用到的数据表。

虚拟机指令类型

/*
**虚拟机指令类型;;
**必须是无符号的(至少)4字节(请参阅lopcode .h中的详细信息)
*/
#if LUAI_BITSINT >= 32
typedef unsigned int Instruction;
#else
typedef unsigned long Instruction;
#endif

2、Lua的数据类型

(如果要看源码了,可以看一下会被gc的那个类型数据,是如何被定义的,为啥lua不需要定义数据类型就可以赋值?什么要的数据类型会被放到_G那里去。然后可能还有一些数据类型不不会暴露给我们使用的,比如Proto,这个跟function的实现相关,有兴趣可以了解一下。还有lua_State)

  • Lua 中有 8 个基本类型分别为:nil、boolean、number、string、userdata、function、thread 和 table。

为啥lua不需要定义数据类型就可以赋值?

在赋值的时候,会调用函数expr解析表达式,=号右边的值,它最终会走入函数simpleexp中,在simpleexp中会根据expr解析出来的expdesc结构体里的t.token,用一个switch判断该表达式的类型,初始化expdesc结构体,将具体的数据赋值给expdesc结构体中的nval,所以,lua不需要定义数据类型就可以赋值,因为在解析器中会根据值的类型来进行初始化。

函数localstat中,会读取“=”号左边的所有变量,首先看到在函数localstat中,首先会有一个循环调用函数new_localvar,将“=”左边的所有以","分隔的变量都生成一个相应的局部变量。 每一个局部变量,存储它的信息时使用的是LocVar结构体

static void localstat (LexState *ls) {/* stat -> LOCAL NAME {',' NAME} ['=' explist] */int nvars = 0;int nexps;expdesc e;do {new_localvar(ls, str_checkname(ls));nvars++;} while (testnext(ls, ','));if (testnext(ls, '='))nexps = explist(ls, &e);else {e.k = VVOID;nexps = 0;}adjust_assign(ls, nvars, nexps, &e);adjustlocalvars(ls, nvars);
}typedef struct LocVar {TString *varname;int startpc;  /* first point where variable is active */int endpc;    /* first point where variable is dead */
} LocVar;

这里主要存储了变量名,放在该结构体的变量varname中。一个函数的所有局部变量的LocVar信息,是存放在Proto结构体的locvars中。 在函数localstat中,会读取“=”号左边的所有变量,创建相应的局部变量信息在Proto结构体中。


我们从通过lua_pushbollean等指令函数看,c通过这些函数将各种类型的值压入lua栈,从而传递给lua。

(lapi.c) 556行
LUA_API void lua_pushboolean (lua_State *L, int b) {lua_lock(L);setbvalue(L->top, (b != 0));  /* ensure that true is 1 */api_incr_top(L);lua_unlock(L);
}(lobject.h) 225行
#define setsvalue(L,obj,x) { TValue *io = (obj); TString *x_ = (x); val_(io).gc = obj2gco(x_); settt_(io, ctb(x_->tt)); checkliveness(L,io); }

可以看到从虚拟栈里取出top之后,把值传给了setbvalue(L,obj,x)。

而在 setbvalue 里,obj 被转换成了 TValue 类型,接着又调用了两个宏 val_(),settt_()来设置 TValue 类型的两个成员。

由此可见,lua 栈中所有类型的值都是用 TValue 结构体来表示的。

那么TValue结构体是什么样的呢?

(lobject.h) 110行
#define TValuefields    Value value_; int tt_typedef struct lua_TValue {TValuefields;
} TValue;

它由一个实际的 value 和一个int类型的 tag 组成。

基本类型

(lua.h)
/*
** basic types
*/
#define LUA_TNONE (-1)          // 无类型
#define LUA_TNIL 0              // 空类型
#define LUA_TBOOLEAN 1          // 布尔
#define LUA_TLIGHTUSERDATA 2    // 指针 (void *)
#define LUA_TNUMBER 3           // 数字 (lua_Number)
#define LUA_TSTRING 4           // 字符串 (TString)
#define LUA_TTABLE 5            // 表 (Table)
#define LUA_TFUNCTION 6         // 函数 (CClosure)
#define LUA_TUSERDATA 7         // 指针 (void *)
#define LUA_TTHREAD 8           // LUA虚拟机 (lua_State)

value_ 是一个 union 类型 Value,所以它可以存储多种类型的值,根据注释可知全称叫Tagged Values

(lobject.h 100行)
/*
** Tagged Values. This is the basic representation of values in Lua,
** an actual value plus a tag with its type.
*//*
** Union of all Lua values
*/
typedef union Value {GCObject *gc;    /* collectable objects */void *p;         /* light userdata */int b;           /* booleans */lua_CFunction f; /* light C functions */lua_Integer i;   /* integer numbers */lua_Number n;    /* float numbers */
} Value;


Lua内部用一个宏,表示哪些数据类型需要进行gc操作的: (lobject.h)

#define iscollectable(o)    (rttype(o) & BIT_ISCOLLECTABLE)/* TValue的原始类型标签*/
#define rttype(o)   ((o)->tt_)/*可收集类型的位标记*/
#define BIT_ISCOLLECTABLE   (1 << 6)#define rttype(o) ((o)->tt_)

可以看到,tt_的第六位用于标记类型是否需要进行垃圾回收,

可进行垃圾回收的类型:GCObject

/*
** Common type has only the common header
*/
struct GCObject {CommonHeader;
};#define CommonHeader    GCObject *next; lu_byte tt; lu_byte marked

可以看到GCObject结构中只有一个CommonHeader,CommonHeader主要由一个指向下一个回收类型的指针,一个对象类型tt和一个对象标记marked组成。

所以,lua中所有类型都的结构示意图如下:

TValue 里不是已经有一个 tt_ 字段用于表示类型了吗?为什么在 GCObject 里还需要这个字段呢?

答:要从 GCObject 反向得到 TValue 是不行的,假如 GCObject 没有 tt 字段,单单持有 GCObject 的时候,没法判断这个 GCObject 的类型是什么。 GC 在回收对象的时候需要根据类型来释放资源。基于第一点,必须在 GCObject 里加一个表示类型的字段 tt。

作者:董哒哒 链接:https://www.jianshu.com/p/ad30f77bd7d6

3、为什么说Lua一切皆Table,Table有哪两种存储形式,Table是如何Resize的

  • Lua的table是由数组部分(array part)和哈希部分(hash part)组成。数组部分索引的key是1~n的整数,哈希部分是一个哈希表(open address table),哈希表本质是一个数组,它利用哈希算法将键转化为数组下标,若下标有冲突(即同一个下标对应了两个不同的键),则它会将冲突的下标上创建一个链表,将不同的键串在这个链表上,这种解决冲突的方法叫做:链地址法。
  • table 最基础的作用就是当成字典来用。 它的 key 值可以是除了 nil 之外的任何类型的值,当把 table 当成字典来用时,可以使用 ==pairs== 函数来进行遍历,使用==pairs==进行遍历时的顺序是随机的,事实上相同的语句执行多次得到的结果是不一样的。
  • 当 key 为整数时,table 就可以当成数组来用。而且这个数组是一个 ==索引从1开始== ,没有固定长度,可以根据需要自动增长的数组,我们可以使用使用 ipairs 对数组进行遍历。
  • 其他语言提供的所有结构---数组,记录,列表,队列,集合这些在lua中都用==table==来表示。
  • 向table中插入数据时,如果已经满了,Lua会重新设置数据部分或哈希表的大小,容量是成倍增加的,哈希部分还要对哈希表中的数据进行整理。需要特别注意的没有赋初始值的table,数组和部分哈希部分默认容量为0。
  • resize代价高昂,当我们把一个新键值赋给表时,若数组和哈希表已经满了,则会触发一个再哈希(rehash)。再哈希的代价是高昂的。首先会在内存中分配一个新的长度的数组,然后将所有记录再全部哈希一遍,将原来的记录转移到新数组中。新哈希表的长度是最接近于所有元素数目的2的乘方。
local a = {}     --容量为0
a[1] = true      --重设数组部分的size为1
a[2] = true      --重设数组部分的size为2
a[3] = true      --重设数组部分的size为4local b = {}     --容量为0
b.x = true       --重设哈希部分的size为1
b.y = true       --重设哈希部分的size为2
b.z = true       --重设哈希部分的size为4

4、Lua的面向对象实现

所以,实际上,class.new是什么呢?然后new完之后,返回的是什么东西?

  • 使用元方法模拟面向对象的实现
--[[
云风的lua面向对象编程架构,用来模拟一个基类
--]]local _class={}function class(super)local class_type={}class_type.ctor=falseclass_type.super=super--[[模拟构造函数的function--]]class_type.new=function(...) local obj={}dolocal createcreate = function(c,...)--如果本类存在着基类,就递归调用基类的创建函数初始化基类的成员if c.super thencreate(c.super,...)end-- 如果本类有构造函数,就执行本类的构造函数操作if c.ctor thenc.ctor(obj,...)endend--前面的这段代码是声明create function,下面的就是执行create(class_type,...)end--将此对象的元表的__index元方法设为下面的虚函数表setmetatable(obj,{ __index=_class[class_type] })return objend-- 用一个table来构造类的函数表local vtbl={}_class[class_type]=vtbl--[[设置表class_type的元表并定义__newindex字段,字段对应的函数,参数1就是表class_type本身,当添加一个新方法的时候就会执行此__newindex的实现--]]setmetatable(class_type,{__newindex=function(t,k,v)vtbl[k]=vend})--[[如果本类有父类的话,将本类虚函数表的原表__index设从父类的函数表,直接从父类的函数表中查找。--]]if super thensetmetatable(vtbl,{__index=function(t,k)local ret=_class[super][k]  --这里,就是查找父类的函数表的操作vtbl[k]=retreturn retend})endreturn class_type
end

5、Lua元表是什么?

  • 元表主要用于对两个table进行操作,例如两个table相加,当Lua试图对两个表进行相加时,先检查两者之一是否有元表,之后检查是否有一个叫"add"的字段,若找到,则调用对应的值。"add"等即时字段,其对应的值(往往是一个函数或是table)就是"元方法"。

6、Lua的gc机制简述

  • 在Lua5.0及其更早的版本中,Lua的GC是一次性不可被打断的过程,使用的++Mark算法是双色标记算法(Two color mark)++,这样系统中对象的非黑即白,要么被引用,要么不被引用,这会带来一个问题:在GC的过程中如果新加入对象,这时候新加入的对象无论怎么设置都会带来问题,如果设置为白色,则如果处于回收阶段,则该对象会在没有遍历其关联对象的情况下被回收;如果标记为黑色,那么没有被扫描就被标记为不可回收,是不正确的。
  • 为了降低一次性回收带来的性能问题以及双色算法的问题,在Lua5.1后,Lua都采用分布回收以及++三色增量标记清除算法(Tri-color incremental mark and sweep)++
  • 将所有对象分成三个状态:
  • White状态,也就是待访问状态。表示对象还没有被垃圾回收的标记过程访问到。==(白色又分为White0和White1,主要为了解决上面所说到的在GC过程中新加入的对象的处理问题)==
  • Gray状态,也就是待扫描状态。表示对象已经被垃圾回收访问到了,但是对象本身对于其他对象的引用还没有进行遍历访问。
  • Black状态,也就是已扫描状态。表示对象已经被访问到了,并且也已经遍历了对象本身对其他对象的引用。
  • GC流程
每个新创建的对象颜色设置为White
//初始化阶段
遍历root节点中引用的对象,从白色置为灰色,并且放入到Gray节点列表中
//标记阶段
while(Gray集合不为空,并且没有超过本次计算量的上限):
从中取出一个对象,将其置为Black
遍历这个对象关联的其他所有对象:
if 为White
标记为Gray,加入到Gray链表中//回收阶段
遍历所有对象:
if 为White,
没有被引用的对象,执行回收
else
重新塞入到对象链表中,等待下一轮GC

  • 在每个步骤之间,由于程序可以正常执行,所以会破坏当前对象之间的引用关系。black对象表示已经被扫描的对象,所以他应该不可能引用到一个white对象。当程序的改变使得一个black对象引用到一个white对象时,就会造成错误。解决这个问题的办法就是设置barrier。barrier在程序正常运行过程中,监控所有的引用改变。如果一个black对象需要引用一个white对象,存在两种处理办法:
  • 将white对象设置成gray,并添加到gray列表中等待扫描。这样等于帮助整个GC的标识过程向前推进了一步。
  • 将black对象改回成gray,并添加到gray列表中等待扫描。这样等于使整个GC的标识过程后退了一步。

这种垃圾回收方式被称为"++Incremental Garbage Collection++"(简称为"IGC",Lua所采用的就是这种方法。使用"IGC"并不是没有代价的。IGC所检测出来的垃圾对象集合比实际的集合要小,也就是说,有些在GC过程中变成垃圾的对象,有可能在本轮GC中检测不到。不过,这些残余的垃圾对象一定会在下一轮GC被检测出来,不会造成泄露。

7、Lua的全局变量跟local变量的区别,Lua是如何查询一个全局变量的,local的作用域

  • Lua将所有的全局变量保存在一个常规的table中,这个table称之为环境(_G),使 用下面的代码可以打印当前环境中所有全局变量的名称
for n in pairs(_G) do
print(n)
end

  • 在Lua中,要声明全局变量很简单,那就是定义变量的时候,前面不要加上 local。这个神秘的全局环境,其实本质上也是一个table,它把我们创建的全局变量都保存到一个table里了。而这个table的名字是:_G
  • 本地变量定义在一个函数体中, 那么作用域就在函数中.
  • 如果定义在一个控制结构中, 那么就在这个控制结构中.
  • 如果定义在一个文件中, 那么作用域就在这个文件中.

一些lua使用中要注意的点

  1. 使用local,在代码运行前,Lua会把源码预编译成一种中间码,类似于Java的虚拟机。这种格式然后会通过C的解释器进行解释,整个过程其实就是通过一个while循环,里面有很多的switch...case语句,一个case对应一条指令来解析。 自Lua 5.0之后,Lua采用了一种类似于寄存器的虚拟机模式。Lua用栈来储存其寄存器。每一个活动的函数,Lua都会其分配一个栈,这个栈用来储存函数里的活动记录。每一个函数的栈都可以储存至多250个寄存器,因为栈的长度是用8个比特表示的。 有了这么多的寄存器,Lua的预编译器能把所有的local变量储存在其中。这就使得Lua在获取local变量时其效率十分的高。
  2. 如果你有很多非常多的很小的表需要创建时,你可以将其预先填充以避免rehash。

比如:

{true,true,true}

Lua知道这个表有三个元素,所以Lua直接创建了三个元素长度的数组。

所以,当需要创建非常多的小size的表时,应预先填充好表的大小。

lua检测表中是否有某个值_Lua基础知识总结(入职面试题)相关推荐

  1. lua检测表中是否有某个值_Lua检测数组(tabble)中是否包含某个值

    {"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],&q ...

  2. lua检测表中是否有某个值,如何检查表是否包含Lua中的元素?

    Is there a method for checking if a table contains a value ? I have my own (naive) function, but I w ...

  3. cockroachdb mysql_CockroachDB学习笔记——[译]CockroachDB中的SQL:映射表中数据到键值存储...

    CockroachDB学习笔记--[译]CockroachDB中的SQL:映射表中数据到键值存储 原文标题:SQL in CockroachDB: Mapping Table Data to Key- ...

  4. EXCEL中两列合并成一列以及根据相同列的值将表中一列的值复制到另一张表中?

    今在做EXCEL时遇两问题,现和大家分享如下: 1.如何将表中两列值合并到一列,并按照一定的分隔符进行分割,公式如下: 假如:要将F和G的值 合并到H列并按逗号","隔开:  H1 ...

  5. 查找IN语句在表中不存在的值

    查询in在表里不存在的值,从in列表中获取表中不存在的记录,IN语句在表中不存在的值,查找数据库中不存在的记录 假如有一个Table(T_NAME_INFO),表结构如下: ID     NAME 1 ...

  6. Linux中文件描述符1,linux内核中的文件描述符(一)--基础知识简介

    原标题:linux内核中的文件描述符(一)--基础知识简介 Kernel version:2.6.14 CPU architecture:ARM920T Author:ce123(http://blo ...

  7. MySQL工作中的实际用_总结工作中经常用到的mysql基础知识

    总结工作中经常用到的mysql基础知识 发布时间:2020-06-08 11:27:30 来源:51CTO 阅读:217 作者:三月 本文主要给大家介绍工作中经常用到的mysql基础知识,文章内容都是 ...

  8. linux内核中的文件描述符(一)--基础知识简介

    linux内核中的文件描述符(一)--基础知识简介 Kernel version:2.6.14 CPU architecture:ARM920T Author:ce123(http://blog.cs ...

  9. C#基础知识1-深入理解值类型和引用类型

    C#值类型和引用类型这个概念在刚学习的时候应该就知道了.但是我们并没有深入的去理解它.越是基础知识其实才是最有用的.对代码的优化,代码质量的提升都有帮助.通过整理本文章,对很多知识也起到了巩固的作用吧 ...

最新文章

  1. [Usaco2009 Feb]Revamping Trails 道路升级
  2. 虚拟服务器至强,用至强解决瓶颈 桌面虚拟化案例分享
  3. python 递归目录_Python3:递归实现输出目录下所有的文件
  4. QAQorz的训练记录
  5. win7win10 配置wlan热点
  6. 接口测试 java_接口测试--Java
  7. 机器学习如何计算特征的重要性_机器学习之特征工程
  8. php自学目录,PHP学习笔记(二) 了解PHP的基本语法以及目录结构
  9. 电脑格式化的危害_经常重装系统,对电脑会不会有负面影响?
  10. Activity与Fragment间的通信
  11. delphi 如何解决假死
  12. 键盘拆开重新安装步骤_电脑键盘如何维修 电脑键盘常见问题维修技巧【详解】...
  13. scratch制作彩虹猫病毒模拟器
  14. 【智能车】模糊PID控制原理详解与代码实现
  15. 主流的语音芯片ic方案该如何选 这篇文章可以说清楚
  16. Cadence导出Excel格式BOM表
  17. “一品四境”学JAVA——书籍推荐/路径规划
  18. 强不知以为知 怎能善其事
  19. 【设计】死区时间控制
  20. 科林明伦杯哈尔滨理工大学第九届程序设计竞赛

热门文章

  1. R-CNN 最直观的理解
  2. PDM 导出Excel
  3. 线程同步时,哪些操作会释放锁?哪些操作不会释放锁?
  4. 超好用Web草图工具Balsamiq Mockups
  5. 这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧
  6. Hyper-V与VMware的技术特性对比
  7. java递归遍历目录文件
  8. C# 事务提交(非数据库)
  9. ajax请求l类型,jquery 的ajax请求示例和注意事项
  10. linux gdb模式下无反应,Linux,GDB 嵌入式Linux的GDB远程调试的问题--断点没反应