我们都知道Lua是一门动态类型的脚本语言,也就是说同一个变量可以在不同的时刻指向不同类型的数据。例如
local a = nil
a = 1
a = "123"
而在Lua中有8中基础的数据类型:nil(空),boolean(布尔),number(数字),string(字符串),table(表),function(函数),userdata(自定义类型),thread(协程),那这几种基础类型在Lua中是怎么定义的,而Lua又是怎么实现动态类型的呢?
一、C语言中实现通用数据结构的设想
Lua是使用C语言实现的一种脚本语言,那么我们如果我们想在C语言中实现一种新的通用的数据类型一般会怎么去做呢?
定义一个结构体,这个结构体中一定要有一个字段来存储当前结构体所表示的数据类型,同时需要一些字段来存储不同数据类型的具体数据
二、Lua中通用数据结构的实现
2.1基本数据类型宏的定义
先看一下Lua中定义的几种基本数据类型的宏:
//(lua.h)
/*
** basic types
*/
#define LUA_TNONE (-1)
#define LUA_TNIL 0
#define LUA_TBOOLEAN 1
#define LUA_TLIGHTUSERDATA 2
#define LUA_TNUMBER 3
#define LUA_TSTRING 4
#define LUA_TTABLE 5
#define LUA_TFUNCTION 6
#define LUA_TUSERDATA 7
#define LUA_TTHREAD 8
这些宏对应的数据类型如下表:
lua_Number对应的C语言的基本数据类型double,所以Lua中的number类型表示的都是实数(双精度浮点数),Lua中没有整数类型。
//(luaconf.h)
/*
** {==================================================================
@@ LUA_NUMBER is the type of numbers in Lua.
** CHANGE the following definitions only if you want to build Lua
** with a number type different from double. You may also need to
** change lua_number2int & lua_number2integer.
** ===================================================================
*/
#define LUA_NUMBER_DOUBLE
#define LUA_NUMBER double
//(lua.h)
/* type of numbers in Lua */
typedef LUA_NUMBER lua_Number;
LUA_TLIGHTUSERDATA和LUA_TUSERDATA都是void *(指针类型)。根据名字我们知道他们对应的是Lua的userdata基本数据类型,但是两者是有一些区别的。LUA_TLIGHTUSERDATA表示那些内存分配与释放都是由Lua外部的使用者要管理的对象,而LUA_TUSERDATA表示的都是通过Lua来管理生命周期的对象,也就是LUA_TUSERDATA指向的对象是需要加入到Lua的GC(Garbage Collection 垃圾回收)中的。
2.2需要GC的基本数据类型
我们知道Lua有自己的GC机制,那么哪些基础数据类型需要加到GC中,哪些又不需要,怎么区分呢?
在Lua中用一个宏来表示哪些数据类型需要进行GC操作:
//(lobject.h)
#define ttype(o) ((o)->tt)
#define iscollectable(o) (ttype(o) >= LUA_TSTRING)
所以说LUA_TSTRING之前的数据类型是都不需要GC,也就是string,table,function,userdata,thread都需要GC的。
在Lua中需要进行GC操作的数据类型都会有个CommonHeader宏定义的成员,并且这个成员在定义的最开始部分。
/*
** Common Header for all collectable objects (in macro form, to be
** included in other objects)
*/
#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked
/*
** Common header in struct form
*/
typedef struct GCheader {
CommonHeader;
} GCheader;
next : 指向下一个GC链表的成员
tt : 表示数据的类型,即前面的那些表示数据类型的宏
marked :GC时,相关的标记位。
到了这里我们可以使用一个共同体(union),将所有需要进行GC的数据类型囊括起来:
//(lstate.h)
/*
** Union of all collectable objects
*/
union GCObject {
GCheader gch;
union TString ts;
union Udata u;
union Closure cl;
struct Table h;
struct Proto p;
struct UpVal uv;
struct lua_State th; /* thread */
};
所以GCObject可以表示Lua中所有需要GC的数据类型。
2.3Lua中所有数据类型表示
既然所有需要GC的数据类型使用GCObject表示,那么同理所有的数据类型也可以用一个共同体表示:
//(lobject.h)
/*
** Union of all Lua values
*/
typedef union {
GCObject *gc;
void *p;
lua_Number n;
int b;
} Value;
结合我们(一)中所说的,我们现在有了可以表示所有类型数据的Value了,那么就还需要一个表示数据类型的字段,所以Lua中给我们定义了一个TValue的结构体:
//(lobject.h)
/*
** Tagged Values
*/
#define TValuefields Value value; int tt
typedef struct lua_TValue {
TValuefields;
} TValue;
从这个结构体我们可以看到使用int类型的tt字段表示当前的数据类型,使用Value来表示任意类型的值。这样TValue就可以表示Lua中任意的数据类型了。
我们用一个图来表示一下Lua通用的数据结构的组织:
三、通用类型与具体类型转换
3.1判断是否是具体的数据类型
//(lobject.h)
/* Macros to test type */
#define ttype(o) ((o)->tt)
#define ttisnil(o) (ttype(o) == LUA_TNIL)
#define ttisnumber(o) (ttype(o) == LUA_TNUMBER)
#define ttisstring(o) (ttype(o) == LUA_TSTRING)
#define ttistable(o) (ttype(o) == LUA_TTABLE)
#define ttisfunction(o) (ttype(o) == LUA_TFUNCTION)
#define ttisboolean(o) (ttype(o) == LUA_TBOOLEAN)
#define ttisuserdata(o) (ttype(o) == LUA_TUSERDATA)
#define ttisthread(o) (ttype(o) == LUA_TTHREAD)
#define ttislightuserdata(o) (ttype(o) == LUA_TLIGHTUSERDATA)
o为通用数据类型TValue,tt则为TValue结构体中表示具体数据类型的字段。
3.2获得具体数据类型的值
//(lobject.h)
#define gcvalue(o) check_exp(iscollectable(o), (o)->value.gc)
#define pvalue(o) check_exp(ttislightuserdata(o), (o)->value.p)
#define nvalue(o) check_exp(ttisnumber(o), (o)->value.n)
#define rawtsvalue(o) check_exp(ttisstring(o), &(o)->value.gc->ts)
#define tsvalue(o) (&rawtsvalue(o)->tsv)
#define rawuvalue(o) check_exp(ttisuserdata(o), &(o)->value.gc->u)
#define uvalue(o) (&rawuvalue(o)->uv)
#define clvalue(o) check_exp(ttisfunction(o), &(o)->value.gc->cl)
#define hvalue(o) check_exp(ttistable(o), &(o)->value.gc->h)
#define bvalue(o) check_exp(ttisboolean(o), (o)->value.b)
#define thvalue(o) check_exp(ttisthread(o), &(o)->value.gc->th)
#define l_isfalse(o) (ttisnil(o) || (ttisboolean(o) && bvalue(o) == 0))
3.3设置具体数据类型的值
//(lobject.h)
/* Macros to set values */
#define setnilvalue(obj) ((obj)->tt=LUA_TNIL)
#define setnvalue(obj,x) \
{ TValue *i_o=(obj); i_o->value.n=(x); i_o->tt=LUA_TNUMBER; }
#define setpvalue(obj,x) \
{ TValue *i_o=(obj); i_o->value.p=(x); i_o->tt=LUA_TLIGHTUSERDATA; }
#define setbvalue(obj,x) \
{ TValue *i_o=(obj); i_o->value.b=(x); i_o->tt=LUA_TBOOLEAN; }
#define setsvalue(L,obj,x) \
{ TValue *i_o=(obj); \
i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TSTRING; \
checkliveness(G(L),i_o); }
#define setuvalue(L,obj,x) \
{ TValue *i_o=(obj); \
i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TUSERDATA; \
checkliveness(G(L),i_o); }
#define setthvalue(L,obj,x) \
{ TValue *i_o=(obj); \
i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TTHREAD; \
checkliveness(G(L),i_o); }
#define setclvalue(L,obj,x) \
{ TValue *i_o=(obj); \
i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TFUNCTION; \
checkliveness(G(L),i_o); }
#define sethvalue(L,obj,x) \
{ TValue *i_o=(obj); \
i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TTABLE; \
checkliveness(G(L),i_o); }
#define setptvalue(L,obj,x) \
{ TValue *i_o=(obj); \
i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TPROTO; \
checkliveness(G(L),i_o); }
#define setobj(L,obj1,obj2) \
{ const TValue *o2=(obj2); TValue *o1=(obj1); \
o1->value = o2->value; o1->tt=o2->tt; \
checkliveness(G(L),o1); }