IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

lua metatable使用和源码分析(一)

运维和开发 2012-09-02 22:27:47 累计浏览 2,028 次
本机暂存

    lua metatable(以下简称元表)类似c++的operator overloads,可以对复合结构进行操作,在lua里最常见的就是对表的操作.举例来说,当两个表作加法操作的时候,Lua会检查表的元表中是否有”__add”事件是否对应一个函数(metamethod)。如果存在Lua会调用这个函数来执行一次表的加法操作.

    __index和__newindex是表常常要添加的事件,用于处理键值在表无法被查找到之后的处理.

hoterran@~/Projects/lua$ cat meta_test.lua
t = {}
t.a = 1

print(t.a)
print(t.b)
setmetatable(t, {__index = function(x) return "test" end})
print(t.b)

hoterran@~/Projects/lua$ lua meta_test.lua
1
nil
test

    可见当键值”b”在表内无法查找到之后会调用__index事件对应的匿名函数,后面我们从源码角度分析这个例子.

    接下来源码角度分析各种类型的元表.

     对于基本类型的元表,每类类型只有一个元表,某类类型共享同一个元表,基本类型的元表未设置之前都为空.

lstate.c
================
struct global_State{
  ....
  struct Table *mt[NUM_TAGS];  /* metatables for basic types */
  TString *tmname[TM_N];  /* array with tag-method names */
  ....
}

    mt[NUM_TAGS]用于存储lua各种类型的元表,NUM_TAGS就lua类型个数加一.

mt[1] = {__len = func1, __index = func2 ….}   /*boolean*/
mt[2] = {__len = func1, __index = func2 ….}   /*lightuserdata*/
....
mt[NUM_TAGS]

    tmname[TM_N]用于存储元表事件的字符串,位置与TMS一一对应,这张表在lua启动时会初始化.

ltm.c
==========
void luaT_init (lua_State *L) {
    static const char *const luaT_eventname[] = { /* ORDER TM */
    "__index", "__newindex",
    "__gc", "__mode", "__eq",
    "__add", "__sub", "__mul", "__div", "__mod",
    "__pow", "__unm", "__len", "__lt", "__le",
    "__concat", "__call"
};
int i;
for (i=0; i G(L)->tmname[i] = luaS_new(L, luaT_eventname[i]);
    luaS_fix(G(L)->tmname[i]); /* never collect these names */
}
}

    查找某个对象类型的元表直接通过对象类型查找元表

(L)->mt[ttype(o)];

    查找元表的事件函数,先从字节码的TMS查找tmname到事件字符串,然后到元表里以事件字符串作为键值查找函数.

luaH_getstr(mt, G(L)->tmname[event])

    对于表和userdata每个对象都有自己独有的元表,是对象私有的元表而不像基本类型是类型共享元表

struct uv {
…
struct table metatable;
...
}
struct table {
…
struct table metatable;
...
}

    lua程序里的setmetatable,getmetatable实际调用的是luaB_setmetatable/luaB_getmetatable两函数,以下是setmetatable的源码.可见在lua程序里只能为表设置元表

lbaselib.c
================
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"))
    luaL_error(L, "cannot change a protected metatable");
  lua_settop(L, 2);
  lua_setmetatable(L, 1);
  return 1;
}

    而在c程序里,我们就可以对userdata和一些基本类型进行设置元表.使用的函数是lua_setmetatable/lua_getmetatable.

lapi.c
============
LUA_API int lua_setmetatable (lua_State *L, int objindex) {
  TValue *obj;
  Table *mt;
  lua_lock(L);
  api_checknelems(L, 1);
  obj = index2adr(L, objindex);
  api_checkvalidindex(L, obj);
  if (ttisnil(L->top - 1))
    mt = NULL;
  else {
    api_check(L, ttistable(L->top - 1));
    mt = hvalue(L->top - 1);
  }
  switch (ttype(obj)) {
    case LUA_TTABLE: {
      hvalue(obj)->metatable = mt;
      if (mt)
        luaC_objbarriert(L, hvalue(obj), mt);
      break;
    }
    case LUA_TUSERDATA: {
      uvalue(obj)->metatable = mt;
      if (mt)
        luaC_objbarrier(L, rawuvalue(obj), mt);
      break;
    }
    default: {
      G(L)->mt[ttype(obj)] = mt;
      break;
    }
  }
  L->top--;
  lua_unlock(L);
  return 1;
}

    根据类型到不同的位置来设置元表,查找元表简单就不例举了.

    以上是元表的基础知识,接下来讲介绍元表的执行过程.

同分类推荐文章

  1. 科技爱好者周刊(第 401 期):如何赚到10亿美元 (2026-06-26 08:05:38)
  2. 如何做决策 - 从 Go 的一个 issue 说起 (2026-06-26 08:00:00)
  3. Seven Player:Windows上播放115网盘视频的增强工具 (2026-06-09 00:06:47)

查看更多 开发者 文章 →

建议继续学习

  1. javascript事件触发器fireEvent和dispatchEvent (累计阅读 5,775)
  2. Nginx与Lua (累计阅读 5,672)
  3. jQuery延时绑定事件(lazy-bind) (累计阅读 5,515)
  4. Lua GC 的源码剖析 (2) (累计阅读 5,079)
  5. Ameba , 一个简单的 lua 多线程实现 (累计阅读 4,983)
  6. Lua GC 的源码剖析 (4) (累计阅读 4,776)
  7. Proto Buffers in Lua (累计阅读 4,404)
  8. Lua GC 的源码剖析 (1) (累计阅读 4,390)
  9. 一个 Lua 内存泄露检查工具 (累计阅读 4,010)
  10. Lua GC 的源码剖析 (6) 完结 (累计阅读 3,927)