您的位置:首页 > 编程语言 > Lua

快速掌握Lua 5.3 —— userdata (2)

2016-05-08 10:51 351 查看

Q:如何使用”userdata”的”metamethods”?

A:我们继续来修改上一节中的例子,这次我们的目标是使用面向对象的方式调用”userdata”的方法。这个目标既可以在Lua中实现,也可以在C库中实现,我们先来看一个比较简单的方式,在Lua中实现。”mylib.c”中代码无需更改,只需要修改”a.lua”中的代码,

local array = require "mylib"

--[[ 这里创建一个大小为1的数组,仅仅是为了获取其"metatable"。
虽然Lua代码无法更改"userdata"的"metatable",
但是获取"metatable"以及修改"metamethods"是不受影响的。
因为所有通过"array.new"创建的数组,
均使用存储在"registry"中的同一个"metatable"。
所以这里修改了此"metatable"的"metamethods",
下面所创建的数组也就拥有了这些"metamethods"。]]
local metaarray = getmetatable(array.new(1))
metaarray.__index = metaarray
metaarray.set = array.set
metaarray.get = array.get
metaarray.size = array.size

a = array.new(1000)
print(a)    --> userdata: 0x240f268
print(a:size())    --> 1000
for i = 1, 1000 do
a:set(i, 1 / i)
end
print(a:get(10))    --> 0.1


在Lua中实现的方式虽然方便,但在实际应用中却并不推荐。因为提供C库的意义就在于让使用者方便的调用提供的函数,而基于这种实现方式下,难道要在发布的C库的”Readme.txt”中说明在使用时需要增加的代码,以及暴露自己C库中定义的函数名称?

所以为了避免这些问题,我们接下来将在C库中实现”userdata”的”metamethods”。

/* 将"l"中所有的函数注册到从栈顶开始"nup"个"upvalues"下的"table"中,
* 被注册的函数共享"nup"个"upvalues"。
* "nup"个"upvalues"在函数执行完成后会被出栈。
*/
void luaL_setfuncs(lua_State *L, const luaL_Reg *l, int nup);


“mylib.c”文件中:

#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

typedef struct NumArray
{
int size;
double values[1];
} NumArray;

static NumArray *checkarray(lua_State *L)
{
void *ud = luaL_checkudata(L, 1, "LuaBook.array");
luaL_argcheck(L, ud != NULL, 1, "'array' expected");

return (NumArray *)ud;
}

static double *getelem(lua_State *L)
{
NumArray *a = checkarray(L);
int index = luaL_checkinteger(L, 2);

luaL_argcheck(L, 1 <= index && index <= a->size, 2,
"index out of range");

return &a->values[index - 1];
}

static int newarray(lua_State *L)
{
int n = luaL_checkinteger(L, 1);
size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);

luaL_getmetatable(L, "LuaBook.array");
// 注意,这里使用的不是"luaL_setmetatable",两个函数的功能不同。
lua_setmetatable(L, -2);

a->size = n;

return 1;
}

static int setarray(lua_State *L)
{
double value = luaL_checknumber(L, 3);
*getelem(L) = value;

return 0;
}

static int getarray(lua_State *L)
{
lua_pushnumber(L, *getelem(L));

return 1;
}

static int getsize(lua_State *L)
{
NumArray *a = checkarray(L);
lua_pushnumber(L, a->size);

return 1;
}

static int array2string(lua_State *L)
{
NumArray *a = checkarray(L);
lua_pushfstring(L, "array(%d)", a->size);

return 1;
}

/* 现在C库中只需要向外部提供创建数组的方法,
* 那些需要通过对象调用的方法都以"metamethods"的形式放在了"metatable"中。
*/
static const struct luaL_Reg arraylib_f[] = {
{"new", newarray},
{NULL, NULL}
};

/* "metamethods"。
* 因为"userdata"根本就没有"keys",所以每当调用C库中未向外部提供的方法时,
* 都会来寻找对应的"metamethods"。
static const struct luaL_Reg arraylib_m[] = {
// 将对象转换为字符串时,默认会调用的"metamethod"。
{"__tostring", array2string},
{"set", setarray},
{"get", getarray},
{"size", getsize},
{NULL, NULL}
};

extern int luaopen_mylib(lua_State* L)
{
luaL_newmetatable(L, "LuaBook.array");    // 新创建的"metatable"会入栈。

lua_pushstring(L, "__index");
lua_pushvalue(L, -2);    // 复制一份"metatable"再次入栈。
lua_settable(L, -3);    // "metatable.__index = metatable"
// 将"metamethods"都存入"metatable"中。
luaL_setfuncs(L, arraylib_m, 0);

luaL_newlib(L, arraylib_f);

return 1;
}


将”mylib.c”编译为动态连接库,

prompt> gcc mylib.c -fPIC -shared -o mylib.so -Wall
prompt> ls
mylib.c    mylib.so    a.lua


“a.lua”文件中:

local array = require "mylib"

a = array.new(1000)
--[[ 传递给"print"的参数会自动调用对象的"__tostring"方法,
C库中提供了此"metamethod",所以这里不再打印"userdata: ***",
而是打印指定格式的信息。]]
print(a)    --> array(1000)
print(a:size())    --> 1000
for i = 1, 1000 do
a:set(i, 1 / i)
end
print(a:get(10))    --> 0.1


附加:

1、我们还可以使用常规数组的访问方式访问我们在C库中定义的数组,只需要将
metaarray.__index
指向
getarray
metaarray.__newindex
指向
setarray
。由于这种实现方式,
metaarray.__index
只能指向单一的函数,
getsize
array2string
这类函数还是需要定义在
luaL_newlib
接收的数组中。

在Lua中实现:

“mylib.c”文件中:

#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

typedef struct NumArray
{
int size;
double values[1];
} NumArray;

static NumArray *checkarray(lua_State *L)
{
void *ud = luaL_checkudata(L, 1, "LuaBook.array");
luaL_argcheck(L, ud != NULL, 1, "'array' expected");

return (NumArray *)ud;
}

static double *getelem(lua_State *L)
{
NumArray *a = checkarray(L);
int index = luaL_checkinteger(L, 2);

luaL_argcheck(L, 1 <= index && index <= a->size, 2,
"index out of range");

return &a->values[index - 1];
}

static int newarray(lua_State *L)
{
int n = luaL_checkinteger(L, 1);
size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);

luaL_getmetatable(L, "LuaBook.array");
// 注意,这里使用的不是"luaL_setmetatable",两个函数的功能不同。
lua_setmetatable(L, -2);

a->size = n;

return 1;
}

static int setarray(lua_State *L) {
double value = luaL_checknumber(L, 3);
*getelem(L) = value;

return 0;
}

static int getarray(lua_State *L)
{
lua_pushnumber(L, *getelem(L));

return 1;
}

static int getsize(lua_State *L)
{
NumArray *a = checkarray(L);
lua_pushnumber(L, a->size);

return 1;
}

static int array2string(lua_State *L)
{
NumArray *a = checkarray(L);
lua_pushfstring(L, "array(%d)", a->size);

return 1;
}

static const struct luaL_Reg arraylib[] = {
{"new", newarray},
{"size", getsize},
{"set", setarray},
{"get", getarray},
{"tostring", array2string},
{NULL, NULL}
};

extern int luaopen_mylib(lua_State* L)
{
luaL_newmetatable(L, "LuaBook.array");
luaL_newlib(L, arraylib);

return 1;
}


“a.lua”文件中:

local array = require "mylib"

local metaarray = getmetatable(array.new(1))
metaarray.__index = array.get
metaarray.__newindex = array.set

a = array.new(1000)
print(array.tostring(a))    --> array(1000)
print(array.size(a))    --> 1000
for i = 1, 1000 do
a[i] = (1 / i)
end
print(a[10])    --> 0.1


在C库中实现:

“mylib.c”文件中:

#include <stdio.h>
#include <string.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

typedef struct NumArray
{
int size;
double values[1];
} NumArray;

static NumArray *checkarray(lua_State *L)
{
void *ud = luaL_checkudata(L, 1, "LuaBook.array");
luaL_argcheck(L, ud != NULL, 1, "'array' expected");

return (NumArray *)ud;
}

static double *getelem(lua_State *L)
{
NumArray *a = checkarray(L);
int index = luaL_checkinteger(L, 2);

luaL_argcheck(L, 1 <= index && index <= a->size, 2,
"index out of range");

return &a->values[index - 1];
}

static int newarray(lua_State *L)
{
int n = luaL_checkinteger(L, 1);
size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);

luaL_getmetatable(L, "LuaBook.array");
lua_setmetatable(L, -2);

a->size = n;

return 1;
}

static int setarray(lua_State *L) {
double value = luaL_checknumber(L, 3);
*getelem(L) = value;

return 0;
}

static int getarray(lua_State *L)
{
lua_pushnumber(L, *getelem(L));

return 1;
}

static int getsize(lua_State *L)
{
NumArray *a = checkarray(L);
lua_pushnumber(L, a->size);

return 1;
}

static int array2string(lua_State *L)
{
NumArray *a = checkarray(L);
lua_pushfstring(L, "array(%d)", a->size);

return 1;
}

static const struct luaL_Reg arraylib[] = {
{"new", newarray},
{"size", getsize},
// 数组转换为字符串的函数不再作为"metamethod",而是显式的提供给外部。
{"tostring", array2string},
{NULL, NULL}
};

extern int luaopen_mylib(lua_State* L)
{
luaL_newmetatable(L, "LuaBook.array");

lua_pushstring(L, "__index");
lua_pushcfunction(L, getarray);
lua_settable(L, -3);    // metatable.__index = getarray

lua_pushstring(L, "__newindex");
lua_pushcfunction(L, setarray);
lua_settable(L, -3);    // metatable.__newindex = setarray

luaL_newlib(L, arraylib);

return 1;
}


“a.lua”文件中:

local array = require "mylib"

a = array.new(1000)
print(array.tostring(a))    --> array(1000)
print(array.size(a))    --> 1000
for i = 1, 1000 do
a[i] = (1 / i)    -- 常规数组访问方式。
end
print(a[10])    --> 0.1 -- 常规数组访问方式。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  lua