IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

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

    hoterran发表于 2012-02-05 15:46:51
    love 0

    基本类型如何使用元表

    在lua里只能为表设置元表,而在c程序里面可以为基本类型进行元表操作,但上篇博文提到了普通类型的很多操作是不会走到元表,下面的例子针对数字类型,添加多种事件,只有部分事件会生效.下面的例子在c代码里对数字类型添加元表对__add,__len都设置事件.

    mct.c
    ======================
    #include
    #include
    #include 
    
    //__add event function
    static int add(lua_State *L) {
        lua_pushinteger(L, 200);
        return 1;
    }
    //__len event function
    static int len(lua_State *L) {
        lua_pushinteger(L, 111);
        return 1;
    }
    
    int main(int argc, char* argv[]) {
        lua_State *L = lua_open();
        luaopen_base(L);
    
        //integer type metatable
        lua_pushnumber(L, 1);
        lua_newtable(L);
    
        lua_pushcfunction(L, add);
        lua_setfield(L, -2, "__add");
        lua_pushcfunction(L, len);
        lua_setfield(L, -2, "__len");
    
        lua_setmetatable(L, -2);
    
        if(luaL_loadfile(L, "meta_c_test.lua") || lua_pcall(L, 0, 0, 0)) {
            printf("error %s\n", lua_tostring(L, -1));
            return;
        }
    
        lua_close(L);
    
        return 0;
    }

    再来看meta_c_test.lua脚本

    hoterran@~/Projects/lua$ cat meta_c_test.lua
    print(#1)
    print(1 + 2)
    
    print(getmetatable(1))
    print(getmetatable(""))
    
    hoterran@~/Projects/lua$ ./mct
    100
    3
    table: 0x87e7950
    nil

    这个lua程序在普通的lua解释器下是回报错的,因为数字类型并未有计算长度的函数.当给数字类型设置元表后,__len成功的执行了,而__add并未调用元表事件函数,这符合我们在上一篇博文里的分析.同时可以发现数字类型已经设置了一个元表,而其它基本类型的元表都是为nil的.

    利用 _metatable事件来保护元表不会被修改

    _metatable可以隐藏元表信息,一旦设置后元表就不能再被修改了.

    hoterran@~/Projects/lua$ cat meta_metatable.lua
    d = {}
    setmetatable(d, {__index = function(x) return 111 end})
    print(d.aa)                 ---- 111
    print(getmetatable(d))
    setmetatable(d, {__metatable = "aaaa"})
    print(getmetatable(d))       ---- metable信息被隐藏,打印出__metatable里信息aaaa
    --cant call
    --setmetatable(d, {__index = function(x) return 222 end})  ---这句会报错,元表不再能被修改.

    设置元表的时候如果发现有__metatable的事件信息则报错退出,停止设置元表

    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;
    }
    

    查找元表之后,如果还存在__metatable事件,返回该事件对应的信息并把已经获取到的元表弹出栈.

    static int luaB_getmetatable (lua_State *L) {
        luaL_checkany(L, 1);
        if (!lua_getmetatable(L, 1)) {
            lua_pushnil(L);
            return 1;  /* no metatable */
        }
        luaL_getmetafield(L, 1, "__metatable");
        return 1;  /* returns either __metatable field (if present) or metatable */
    }
    

    利用元表来保护userdata不被误用

    因为userdata是个指针容易被误用,所以元表的作用就是给它取个名字,使得在操作userdata的时候比较名字是否一致.这里代码不展示了,介绍一些原理.
    luaL_newmetatable会创建一个键值名和空元表挂到LUA_REGISTRYINDEX,
    lua_setmetatable为userdata设置元表之后,操作userdata的时候会调用lua_checkudata根据键值从REGISTRYINDEX获得这个元表并与userdata内的元表进行比较看是否相同,否则不能操作这个userdata.

    好了基本上metatable的用法就是这些,有不对的地方还请帮忙指出,谢.



沪ICP备19023445号-2号
友情链接