云风的lua源码赏析对元表部分一带而过,估计是觉得太low。幸好还有 hoterran网友在做分析,以下很多文字参考他的。
先看一个例子:
person = {}
person.mp = 1
print(person.hp)
因为person这个table没有hp 这个key会返回nil;
但是如果添加元表就不一样了:
person = {}
person.mp = 1
base = {}
base.hp = 10
setmetatable(base, {__index = function()
return ("hey guy u call a unexisted table member!!")
end})
setmetatable(person, {__index = base})
print(person.hp)
print(person.wp)
输出就会是这样:
>lua -e "io.stdout:setvbuf 'no'" "tt.lua"
10
hey guy u call a unexisted table member!!
>Exit code: 0
这里将base表作为person的元表,如果在person里找不到的key就会向元表里找,hp就找到了,wp找不到就打印那段提示啦,哈哈。
那lua内部是如何实现设置元表并如何在table.key/fun时查询相关元表呢?
1、设置元表
static int luaB_setmetatable (lua_State *L) {
int t = lua_type(L, 2);
luaL_checktype(L, 1, LUA_TTABLE);
luaL_argcheck(L, t == LUA_TNIL || t == LUA_TTABLE, 2,
"nil or table expected");
if (luaL_getmetafield(L, 1, "__metatable"))
return luaL_error(L, "cannot change a protected metatable");
lua_settop(L, 2);
lua_setmetatable(L, 1);
return 1;
}
就table来说,它有metatable字段,调用此函数
2、如何如果设置了__index,那是调用元表?
在
void luaV_execute (lua_State *L) 里会解析到opcode进行事件分发:
for (;;) {
Instruction i = *(ci->u.l.savedpc++);
StkId ra;
if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) &&
(--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)) {
Protect(traceexec(L));
}
/* WARNING: several calls may realloc the stack and invalidate `ra' */
ra = RA(i);
lua_assert(base == ci->u.l.base);
lua_assert(base <= L->top && L->top < L->stack + L->stacksize);
vmdispatch (GET_OPCODE(i)) {
vmcase(OP_GETTABLE,
Protect(luaV_gettable(L, RB(i), RKC(i), ra));
)
这里会调用luaV_gettable
void luaV_gettable (lua_State *L, const TValue *t, TValue *key, StkId val) {
int loop;
for (loop = 0; loop < MAXTAGLOOP; loop++) {
const TValue *tm;
if (ttistable(t)) { /* `t' is a table? */
Table *h = hvalue(t);
const TValue *res = luaH_get(h, key); /* do a primitive get */
if (!ttisnil(res) || /* result is not nil? */
(tm = fasttm(L, h->metatable, TM_INDEX)) == NULL) { /* or no TM? */
setobj2s(L, val, res);
return;
}
/* else will try the tag method */
}
else if (ttisnil(tm = luaT_gettmbyobj(L, t, TM_INDEX)))
luaG_typeerror(L, t, "index");
if (ttisfunction(tm)) {
callTM(L, tm, t, key, val, 1);
return;
}
t = tm; /* else repeat with 'tm' */
}
luaG_runerror(L, "loop in gettable");
}
如果是个table就会查到用fasttm查到元表对应的TValue
/*
** function to be used with macro "fasttm": optimized for absence of
** tag methods
*/
const TValue *luaT_gettm (Table *events, TMS event, TString *ename) {
const TValue *tm = luaH_getstr(events, ename);
lua_assert(event <= TM_EQ);
if (ttisnil(tm)) { /* no tag method? */
events->flags |= cast_byte(1u<<event); /* cache this fact */
return NULL;
}
else return tm;
}
否则判断是否是个函数,如果是就调用。
这样看下来,文章最前面写的lua例子就容易理解了。