第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于更高版本,但有一些不同之处。
第四版针对 Lua 5.3,可在 Amazon 和其他书店购买。
购买本书,您还可以帮助支持 Lua 项目


28.3 – 面向对象访问

我们的下一步是将新类型转换为对象,以便我们可以使用通常的面向对象语法对其实例进行操作,例如

    a = array.new(1000)
    print(a:size())     --> 1000
    a:set(10, 3.4)
    print(a:get(10))    --> 3.4

请记住,a:size() 等效于 a.size(a)。因此,我们必须安排表达式 a.size 返回我们的 getsize 函数。此处的关键机制是 __index 元方法。对于表,每当 Lua 找不到给定键的值时,就会调用此元方法。对于用户数据,每次访问都会调用它,因为用户数据根本没有键。

假设我们运行以下代码

    local metaarray = getmetatable(array.new(1))
    metaarray.__index = metaarray
    metaarray.set = array.set
    metaarray.get = array.get
    metaarray.size = array.size
在第一行中,我们创建一个数组,仅获取其元表,并将其分配给 metaarray。(我们无法从 Lua 设置用户数据的元表,但我们可以毫无限制地获取其元表。)然后,我们将 metaarray.__index 设置为 metaarray。当我们计算 a.size 时,Lua 无法在对象 a 中找到键 "size",因为该对象是用户数据。因此,Lua 将尝试从 a 的元表的 __index 字段获取此值,而该字段恰好是 metaarray 本身。但 metaarray.sizearray.size,因此 a.size(a) 产生 array.size(a),正如我们所愿。

当然,我们可以在 C 中编写相同的内容。我们甚至可以做得更好:现在数组是对象,有自己的操作,我们不再需要在表 array 中进行这些操作。我们的库仍然必须导出的唯一函数是 new,用于创建新数组。所有其他操作仅作为方法提供。C 代码可以直接将它们注册为方法。

操作 getsizegetarraysetarray 与我们之前的方法没有变化。改变的是我们注册它们的方式。也就是说,我们必须更改打开库的函数。首先,我们需要两个单独的函数列表,一个用于常规函数,一个用于方法

    static const struct luaL_reg arraylib_f [] = {
      {"new", newarray},
      {NULL, NULL}
    };
    
    static const struct luaL_reg arraylib_m [] = {
      {"set", setarray},
      {"get", getarray},
      {"size", getsize},
      {NULL, NULL}
    };
打开库的函数 luaopen_array 的新版本必须创建元表,将其分配给它自己的 __index 字段,在那里注册所有方法,并创建和填充 array
    int luaopen_array (lua_State *L) {
      luaL_newmetatable(L, "LuaBook.array");
    
      lua_pushstring(L, "__index");
      lua_pushvalue(L, -2);  /* pushes the metatable */
      lua_settable(L, -3);  /* metatable.__index = metatable */
    
      luaL_openlib(L, NULL, arraylib_m, 0);
    
      luaL_openlib(L, "array", arraylib_f, 0);
      return 1;
    }
这里我们使用 luaL_openlib 的另一个特性。在第一次调用中,当我们传递 NULL 作为库名称时,luaL_openlib 不会创建任何表来打包函数;相反,它假定包表在堆栈上,低于任何偶尔的向上值。在此示例中,包表是元表本身,luaL_openlib 将方法放在此处。对 luaL_openlib 的下一次调用正常工作:它使用给定的名称 (array) 创建一个新表,并在那里注册给定的函数(此例中仅为 new)。

作为最后一步,我们将向新类型添加一个 __tostring 方法,以便 print(a) 打印 array 加上括号内的数组大小(例如,array(1000))。函数本身在此处

    int array2string (lua_State *L) {
      NumArray *a = checkarray(L);
      lua_pushfstring(L, "array(%d)", a->size);
      return 1;
    }
lua_pushfstring 函数格式化字符串并将其留在堆栈顶部。我们还必须将 array2string 添加到列表 arraylib_m,以将其包含在数组对象的元表中
    static const struct luaL_reg arraylib_m [] = {
      {"__tostring", array2string},
      {"set", setarray},
      ...
    };