28.3 访问面向对象的数据

下面我们来看看如何定义类型为对象的userdata,以致我们就可以使用面向对象的语法来操作对象的实例,比如:

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 元方法(metamethod)的使用。对于表来说,不管什么时候只要找不到给定的key,这个元方法就会被调用。对于userdata来讲,每次被访问的时候元方法都会被调用,因为userdata根本就没有任何key

假如我们运行下面的代码:

local metaarray = getmetatable(array.new(1))

metaarray.__index = metaarray

metaarray.set = array.set

metaarray.get = array.get

metaarray.size = array.size

第一行,我们仅仅创建一个数组并获取他的metatablemetatable被赋值给metaarray(我们不能从Lua中设置userdatametatable,但是我们在Lua中无限制的访问metatable)。接下来,我们设置metaarray.__indexmetaarray。当我们计算a.size的时候,Lua在对象a中找不到size这个键值,因为对象是一个userdatum。所以,Lua试着从对象a metatable__index域获取这个值,正好__index就是metaarray。但是metaarray.size就是array.size,因此a.size(a)如我们预期的返回array.size(a)

当然,我们可以在C中完成同样的事情,甚至可以做得更好:现在数组是对象,他有自己的操作,我们在表数组中不需要这些操作。我们实现的库唯一需要对外提供的函数就是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,必须创建一个metatable,并将其赋值给自己的__index域,在那儿注册所有的方法,创建并填充数组表:

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并没有创建任何包含函数的表;相反,他认为封装函数的表在栈内,位于临时的upvalues的下面。在这个例子中,封装函数的表是metatable本身,也就是luaL_openlib放置方法的地方。第二次调用luaL_openlib正常工作:根据给定的数组名创建一个新表,并在表中注册指定的函数(例子中只有一个函数new)。

下面的代码,我们为我们的新类型添加一个__tostring方法,这样一来print(a)将打印数组加上数组的大小,大小两边带有圆括号(比如,array(1000)):

int array2string (lua_State *L) {

    NumArray *a = checkarray(L);

    lua_pushfstring(L, "array(%d)", a->size);

    return 1;

}

函数lua_pushfstring格式化字符串,并将其放到栈顶。为了在数组对象的metatable中包含array2string,我们还必须在arraylib_m列表中添加array2string

static const struct luaL_reg arraylib_m [] = {

    {"__tostring", array2string},

    {"set", setarray},

    ...

};


相关链接:
lua程序设计目录 - 中国lua开发者 - lua论坛