快速掌握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中的表的概念及其相关操作方法
- Lua编程示例(二):面向对象、metatable对表进行扩展
- 把Lua编译进nginx步骤方法
- Lua脚本自动生成APK包
- Lua中的元表(metatable)、元方法(metamethod)详解
- Lua中的metatable介绍
- Lua中ipair和pair的区别
- Lua中的函数精讲笔记
- 浅谈Lua的面向对象特性
- 详解Lua中的变量相关知识点
- Lua脚本语言入门笔记
- Lua脚本调用外部脚本
- 详解Lua中的if语句的使用方法
- Lua中调用函数使用点号和冒号的区别
- Lua中的闭合函数、非全局函数与函数的尾调用详解
- Lua中强大的元方法__index详解
- Lua中调用C++函数示例
- Lua面向对象之类和继承浅析
- Lua性能优化技巧(一):前言
- Lua中获取table长度问题探讨