lua中的自定义类型:userdata

本节是对lua中的userdata的一个小小总结,其示例参考自《Lua程序设计》(第四版)

问题:在lua中使用布尔数组。

虽然lua中可以使用表(table)来实现布尔数组,但是其空间利用率极低。

因此,可以使用lua中的自定义类型实现。

1.位操作

首先是和位相关的操作,如下所示:

//对于x,获取第y位的值
#define getbit(x,y) (((x)>>(y)&1)
//对于x,把第y位置1。先把1左移y位,然后进行位或操作
#define setbit(x,y) x |= (1<<y)
//对于x,把第y位置0。先把1左移y位,然后取反,与x做位与操作
#define clrbit(x,y) x&=~(1<<y)

 以上三种是在本示例中会用到的宏函数(宏替换)

2.内存对齐

这里会提到内存对齐的原因是和布尔数组的创建长度有关。内存对齐的文章有很多,这里不再赘述。 

3.元表(metatable)和元方法(metamethod)

元表可以修改一个值在面对一个未知操作时的行为。例如,假设a和b都是表,那么可以通过元表定义lua语言如何计算表达式a+b,当lua语言视图将两个表相加时,它会先检查两者之一是否有元表企鹅该元表中是否有__add字段。如果找到了该字段,就调用该字段的值。(摘自《lua程序设计》第四版)。元方法类似于c++中的运算符重载,使用元表和元方法可以重新定义一些常见的行为。比如

  • __index 当访问一个表中不存在的字段时,解释器会首先查找一个名为__index的元方法,如果没有这个元方法,则会返回nil,否则,则由这个元方法来提供最终的结果。
  • __newindex 类似于__index,不同则在于__newindex用于表的更新而__index用于表的查询。当对一个表中不存在的索引赋值时,解释器会查找__newindex元方法,如果这个存在,那么解释器就调用它而不执行赋值。
  • __len lua中求表长度的语法是在表的前面加上#,比如表名为set,那么set的长度为#set,当解释器发现#set时,会查找__len的元方法,如果有,则调用该元方法并返回值。

4.userdata

最后则是本节的重头戏。

首先,需要构建一个结构体,用于保存布尔数组:

typedef struct BitArray
{
	int size;
	unsigned int values[1]; //可变部分
} BitArray;

在BitArray中,values为可变部分,详细内容可以参考这个帖子。简单的说,这种空数组的形式,可以理解为指针,但是要比指针更加的方便和安全。

对于32位的机器来说,根据默认的内存对齐,那么BitArray的总字节数为8个字节,也就是说,values一开始就有4个字节的空间(这个需要注意)。

然后是声明一些宏:

#define CHAR_BIT 8
//无符号整型的位数
#define BITS_PER_WORD (CHAR_BIT * sizeof(unsigned int))
//根据指定的索引计算存放相应比特位的字
#define I_WORD(i) ((unsigned int)(i) / BITS_PER_WORD)
//计算访问这个字中相应比特位中要用的掩码
#define I_BIT(i) (1 << ((unsigned int)(i) % BITS_PER_WORD))

#define checkarray(pL) (BitArray*)luaL_checkudata(pL, 1, "LuaBook.array")

BITS_PER_WORD表示unsigned int所占的位数;I_WORD表示对于索引i来说,它在哪一个无符号整型上,而I_BIT则对应了一个掩码,通过它可以方便地取出或更改索引i所对应的值。而checkarray则相对来说比较复杂,它的主要功能就是用于标识本节所实现的布尔数组,必须要有一个名为LuaBook.array的元表。

static int newarray(lua_State* pL)
{
	int i;
	size_t nbytes;
	BitArray* array;
	//比特位的个数
	int n = (int)luaL_checkinteger(pL, 1);
	luaL_argcheck(pL, n >= 1, 1, "invalid size");
	//和内存对齐有关 BitArray 12字节 vlaues 之后默认有4个字节
	nbytes = sizeof(BitArray) + I_WORD(n - 1) * sizeof(unsigned int);
	array = lua_newuserdata(pL, nbytes);
	//设置元表
	luaL_getmetatable(pL, "LuaBook.array");
	lua_setmetatable(pL, -2);
	//初始化
	array->size = n;
	for (i = 0; i < I_WORD(n - 1); i++)
		array->values[i] = 0;
	return 1;
}

首先是创建布尔数组,该方法需要一个值,该值用于标识布尔数组的长度,newarray函数会根据这个长度调用lua_newusedata()来分配内存。需要注意的是,分配的布尔数组的内存可能比正常用到的要小一个字节,不过由于内存对齐的缘故,而多了4B,所以综合来看,上面分配内存会多3B或4B。

lua_newuserdata会返回一个开辟好的内存空间,并把该空间同时压入栈顶。后面的程序为这个userdata分配了一个名为LuaBook.array的元表。最后则是对布尔数组进行初始化为全0。

static unsigned int *getparams(lua_State* pL, unsigned int *mask)
{
    BitArray* array = checkarray(pL);
    int index = (int)luaL_checkinteger(pL, 2) - 1;

    luaL_argcheck(pL, 0 <= index && index < array->size, 2, "index out of range");

    *mask = I_BIT(index);
    return &array->values[I_WORD(index)];
}

getparams函数,用于从lua_State中获取到合法的BitArray,并返回索引index所对应的unsigned int(大小为4B的内存块)和掩码mask。

static int setarray(lua_State* pL)
{
	unsigned int mask;
	unsigned int *entry = getparams(pL, &mask);
	luaL_checkany(pL, 3);

	if (lua_toboolean(pL, 3))
		*entry |= mask;
	else
		*entry &= ~mask;
	return 0;
}

该函数会根据第三个参数的值,来觉得对索引为index,是置一操作还是清零操作。

static int getarray(lua_State* pL)
{
	unsigned int mask;
	unsigned int *entry = getparams(pL, &mask);

	lua_pushboolean(pL, *entry & mask);
	return 1;
}

该函数用于获取index对应的位的值。

static int getsize(lua_State* pL)
{
	BitArray* array = checkarray(pL);
	luaL_argcheck(pL, array != NULL, 1, "'array' expected");
	lua_pushinteger(pL, array->size);
	return 1;
}

getsize函数用于获取布尔数组的长度,并返回。

static const struct luaL_Reg arraylib_m[] = 
{
	{"set", setarray},
	{"get", getarray},
	{"size", getsize},
	{"__tostring", array2string},
	{"__newindex", setarray},
	{"__index", getarray},
	{"__len", getsize},
	{NULL, NULL}
};

static const struct luaL_Reg arraylib_f[] = 
{
	{"new", newarray},
	{NULL, NULL}
};

这两个变量的作用下面会提到。

int lua_openarray(lua_State* pL)
{
	//创建元表
	luaL_newmetatable(pL, "LuaBook.array");
	//复制元表
    /*
	lua_pushvalue(pL, -1);
	//mt.__index = mt
	lua_setfield(pL, -2, "__index");
    */
	//注册元方法
	luaL_setfuncs(pL, arraylib_m, 0);
	//创建一个新的表,并把这些函数注册到这个表中
	luaL_newlib(pL, arraylib_f);
	return 1;
}

lua_openarray函数是加载布尔数组的重要一步,在该函数中,首先创建一个名称为LuaBook.array的元表,并复制了移分,然后让 mt.__index = mt,接着把arraylib_m中的方法全部都注册到了这个元表之中,这一步我目前有些疑问(经我个人测试,上面的那两句注释后不影响)。

lua_newlib(pL, arraylib_f)创建了一个新的表,并把这些函数注册到这个表中。

在lua_openarray方法中,虽然创建了LuaBook.array元表,但是绑定元表的过程在newarray中。

#include <cstdio>
#include <string>

#include "lua.hpp"

extern "C"
{
	#include "bool_array.h"
}

int main()
{
	char buffer[256];
	int error = 0;

	//打开lua
	lua_State* pL = luaL_newstate();
	//打开所有的标准库
	luaL_openlibs(pL);
	//注册函数
	luaL_requiref(pL, "array", lua_openarray, 1);
	lua_pop(pL, 1);

	while (fgets(buffer, sizeof(buffer), stdin) != NULL)
	{
		//编译用户输入的每一行内容,并向栈中压入编译后得到的函数
		//以保护模式弹出编译后的函数运行
		error = luaL_loadstring(pL, buffer) || lua_pcall(pL, 0, 0, 0);
		if (error)
		{
			fprintf(stderr, "%s\n", lua_tostring(pL, -1));
			lua_pop(pL, 1);
		}
	}
	lua_close(pL);
	return 0;
}

然后则是把布尔数组加载进来,它主要的代码是这句:

	//注册函数
	luaL_requiref(pL, "array", lua_openarray, 1);

到这一步,我们已经注册了一个array.new方法在lua环境中了。

运行上述程序,我们就可以愉快的玩耍了,示例如下:

a = array.new(100)
a[3] = true
print(a[3])
print(a)

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值