lua给userdata设置元表_Lua基础知识总结(⼊职⾯试题)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不需要定义数据类型就可以赋值?
函数simpleexp中,在simpleexp中会根据expr
expr解析出来函数expr解析表达式,=号右边的值,它最终会⾛⼊函数simpleexp
在赋值的时候,会调⽤函数expr
expdesc结构体⾥的t.token
所以,lua不需要定义数据类型就可以赋值,因为在解析器中会根据值的类型来进⾏初始化。
nval,所以,lua不需要定义数据类型就可以赋值,因为在解析器中会根据值的类型来进⾏初始化。
函数new_localvar,
函数localstat中,⾸先会有⼀个循环调⽤函数new_localvar
函数localstat中,会读取“=”号左边的所有变量,⾸先看到在函数localstat
在函数localstat
LocVar结构体:
将“=”左边的所有以","分隔的变量都⽣成⼀个相应的局部变量。 每⼀个局部变量,存储它的信息时使⽤的是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。
我们从通过lua_pushbollean
(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_
Tagged Values。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。
3、为什么说Lua⼀切皆Table,Table有哪两种存储形式,Table是如何Resize的
Lua的table是由数组部分(array part)和哈希部分(hash part)组成。数组部分索引的key是1~n的整数,哈希部分是⼀个哈希表(open address table),哈希表本质是⼀个数组,它利⽤哈希算法将键转化为数组下标,若下标有冲突(即同⼀个下标对应了两个不同的键),则它会将冲突的下标上创建⼀个链表,将不同的键串在这个链表上,这种解决冲突的⽅法叫做:链地址法。
table 最基础的作⽤就是当成字典来⽤。 它的 key 值可以是除了 nil 之外的任何类型的值,当把 table 当成字典来⽤时,可以使⽤==pairs== 函数来进⾏遍历,使⽤==pairs==进⾏遍历时的顺序是随机的,事实上相同的语句执⾏多次得到的结果是不⼀样的。
索引从1开始== ,没有固定长度,可以根据需要⾃动增长的当 key 为整数时,table 就可以当成数组来⽤。⽽且这个数组是⼀个 ==索引从1开始
数组,我们可以使⽤使⽤ ipairs 对数组进⾏遍历。
其他语⾔提供的所有结构---数组,记录,列表,队列,集合这些在lua中都⽤==table==来表⽰。
向table中插⼊数据时,如果已经满了,Lua会重新设置数据部分或哈希表的⼤⼩,容量是成倍增加的,哈希部分还要对哈希表中的数据进⾏整理。需要特别注意的没有赋初始值的table,数组和部分哈希部分默认容量为0。
resize代价⾼昂,当我们把⼀个新键值赋给表时,若数组和哈希表已经满了,则会触发⼀个再哈希(rehash)。再哈希的代价是⾼昂resize
的。⾸先会在内存中分配⼀个新的长度的数组,然后将所有记录再全部哈希⼀遍,将原来的记录转移到新数组中。新哈希表的长度是最接近于所有元素数⽬的2的乘⽅。
local a = {}    --容量为0
a[1] = true      --重设数组部分的size为1
a[2] = true      --重设数组部分的size为2
a[3] = true      --重设数组部分的size为4
local b = {}    --容量为0
b.x = true      --重设哈希部分的size为1
b.y = true      --重设哈希部分的size为2
b.z = true      --重设哈希部分的size为4
4、Lua的⾯向对象实现
所以,实际上,w是什么呢?然后new完之后,返回的是什么东西?
使⽤元⽅法模拟⾯向对象的实现
--[[
云风的lua⾯向对象编程架构,⽤来模拟⼀个基类
--]]
local _class={}
function class(super)
local class_type={}
=false
class_type.super=super
--[[
模拟构造函数的function
--]]
w=function(...)
local obj={}
do
local create
create = function(c,...)
--如果本类存在着基类,就递归调⽤基类的创建函数初始化基类的成员
if c.super then
create(c.super,...)
end
-- 如果本类有构造函数,就执⾏本类的构造函数操作
then
<(obj,...)
end
end
--前⾯的这段代码是声明create function,下⾯的就是执⾏
create(class_type,...)
end
--将此对象的元表的__index元⽅法设为下⾯的虚函数表
setmetatable(obj,{ __index=_class[class_type] })
return obj
end
-- ⽤⼀个table来构造类的函数表
local vtbl={}
_class[class_type]=vtbl
--[[
设置表class_type的元表并定义__newindex字段,字段对应的函数,
怎么给数组赋值
参数1就是表class_type本⾝,当添加⼀个新⽅法的时候就会执⾏此__newindex的实现    --]]
setmetatable(class_type,{__newindex=

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。