void lua_rawgeti (lua_State *L, int index, int key); void lua_rawseti (lua_State *L, int index, int key);关于的lua_rawgeti 和 lua_rawseti 的描述有些使人糊涂,因为它涉及到两个索引: index 指向 table 在栈中的位置;key 指向元素在 table 中的位置。当 t 使用负索引的时候 (otherwise,you must compensate for the new item in the stack),调用lua_rawgeti(L,t,key) 等价于:
lua_pushnumber(L, key); lua_rawget(L, t);
调用 lua_rawseti(L, t, key) (也要求 t 使用负索引)等价于:
lua_pushnumber(L, key); lua_insert(L, -2); /* put 'key' below previous value */ lua_rawset(L, t);注意这两个寒暑都是用 raw 操作,他们的速度较快,总之,用作数组的 table 很少使 用 metamethods。 下面看如何使用这些函数的具体的例子,我们将前面的 l_dir 函数的循环体:
lua_pushnumber(L, i++); /* key */ lua_pushstring(L, entry->d_name); /* value */ lua_settable(L, -3);改写为:
lua_pushstring(L, entry->d_name); /* value */ lua_rawseti(L, -2, i++); /* set table at key 'i' */下面是一个更完整的例子,下面的代码实现了 map 函数:以数组的每一个元素为参 数调用一个指定的函数,并将数组的该元素替换为调用函数返回的结果。
int l_map (lua_State *L) { int i, n; /* 1st argument must be a table (t) */ luaL_checktype(L, 1, LUA_TTABLE); /* 2nd argument must be a function (f) */ luaL_checktype(L, 2, LUA_TFUNCTION); n = luaL_getn(L, 1); /* get size of table */ for (i=1; i<=n; i++) { lua_pushvalue(L, 2); /* push f */ lua_rawgeti(L, 1, i); /* push t[i] */ lua_call(L, 1, 1); /* call f(t[i]) */ lua_rawseti(L, 1, i); /* t[i] = result */ } return 0; /* no results */ }
lua_pushlstring(L, s+i, j-i+1);下面这个例子,假如你想写一个函数来根据指定的分隔符分割一个字符串,并返回 一个保存所有子串的 table,比如调用:
split("hi,,there", ",")应该返回表{"hi", "", "there"}。我们可以简单的实现如下,下面这个函数不需要额外 的缓冲区,可以处理字符串的长度也没有限制。
static int l_split (lua_State *L) { const char *s = luaL_checkstring(L, 1); const char *sep = luaL_checkstring(L, 2); const char *e; int i = 1; lua_newtable(L); /* result */ /* repeat for each separator */ while ((e = strchr(s, *sep)) != NULL) { lua_pushlstring(L, s, e-s); /* push substring */ lua_rawseti(L, -2, i++); s = e + 1; /* skip separator */ } /* push last substring */ lua_pushstring(L, s); lua_rawseti(L, -2, i); return 1; /* return the table */ }在 Lua API 中提供了专门的用来连接字符串的函数lua_concat。等价于 Lua 中的..操 作符:自动将数字转换成字符串,如果有必要的时候还会自动调用 metamethods。另外, 她可以同时连接多个字符串。调用 lua_concat(L,n)将连接(同时会出栈)栈顶的 n 个值,并将最终结果放到栈顶。
const char *lua_pushfstring (lua_State *L,const char *fmt, ...);这个函数某种程度上类似于 C 语言中的 sprintf,根据格式串 fmt 的要求创建一个新的字符串。与 sprintf 不同的是,你不需要提供一个字符串缓冲数组,Lua 为你动态的创建新的字符串,按他实际需要的大小。也不需要担心缓冲区溢出等问题。这个函数会将结果字符串放到栈内,并返回一个指向这个结果串的指针。当前,这个函数只支持下列几个指示符: %%(表示字符 '%')、%s(用来格式化字符串)、%d(格式化整数)、%f (格式化 Lua 数字,即 doubles)和 %c(接受一个数字并将其作为字符),不支持宽度 和精度等选项。
static int str_upper (lua_State *L) { size_t l; size_t i; luaL_Buffer b; const char *s = luaL_checklstr(L, 1, &l); luaL_buffinit(L, &b); for (i=0; i<l; i++) luaL_putchar(&b, toupper((unsigned char)(s[i]))); luaL_pushresult(&b); return 1; }使用 Auxlib 中 buffer 的第一步是使用类型 luaL_Buffer 声明一个变量,然后调用 luaL_buffinit 初始化这个变量。初始化之后,buffer 保留了一份状态 L 的拷贝,因此当我 们调用其他操作 buffer 的函数的时候不需要传递 L。宏 luaL_putchar 将一个单个字符放 入 buffer。Auxlib 也提供了 luaL_addlstring 以一个显示的长度将一个字符串放入 buffer, 而 luaL_addstring 将一个以 0 结尾的字符串放入 buffer。最后,luaL_pushresult 刷新 buffer 并将最终字符串放到栈顶。这些函数的原型如下:
void luaL_buffinit (lua_State *L, luaL_Buffer *B); void luaL_putchar (luaL_Buffer *B, char c); void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l); void luaL_addstring (luaL_Buffer *B, const char *s); void luaL_pushresult (luaL_Buffer *B);使用这些函数,我们不需要担心 buffer 的分配,溢出等详细信息。正如我们所看到 的,连接算法是有效的。函数 str_upper 可以毫无问题的处理大字符串(大于 1MB)。
luaL_addstring(&b, lua_tostring(L, 1)); /* BAD CODE */(上面正好构成了一对矛盾),由于这种情况是很常见的,auxlib 提供了特殊 的函数来将位于栈顶的值放入 buffer:
void luaL_addvalue (luaL_Buffer *B);当然,如果位于栈顶的值不是字符串或者数字的话,调用这个函数将会出错。
26.3.1The Registry
registry 一 直位于一个 由 LUA_REGISTRYINDEX 定义的值所对应的假索引 (pseudo-index)的位置。一个假索引除了他对应的值不在栈中之外,其他都类似于栈中的 索引。Lua API 中大部分接受索引作为参数的函数,也都可以接受假索引作为参数一除 了那些操作栈本身的函数,比如 lua_remove,lua_insert。例如,为了获取以键值 "Key" 保 存在 registry 中的值,使用下面的代码:<pre name="code" class="csharp">lua_pushstring(L, "Key"); lua_gettable(L, LUA_REGISTRYINDEX);registry 就是普通的 Lua 表,因此,你可以使用任何非 nil 的Lua值来访问她的元素。 然而,由于所有的 C 库共享相同的 registry ,你必须注意使用什么样的值作为 key,否 则会导致命名冲突。一个防止命名冲突的方法是使用 static 变量的地址作为 key;C 链接器保证在所有的库中这个 key 是唯一的。函数 lua_pushlightuserdata 将一个代表 C 指针的值放到栈内,下面的代码展示了使用上面这个方法,如何从 registry 中获取变量和向 registry 存储变量:
/* variable with an unique address */ static const char Key = 'k'; /* store a number */ lua_pushlightuserdata(L, (void *)&Key); /* push address */ lua_pushnumber(L, myNumber); /* push value */ /* registry[&Key] = myNumber */ lua_settable(L, LUA_REGISTRYINDEX); /* retrieve a number */ lua_pushlightuserdata(L, (void *)&Key); /* push address */ lua_gettable(L, LUA_REGISTRYINDEX); /* retrieve value */ myNumber = lua_tonumber(L, -1); /* convert to number */
我们会在 27.5 节中更详细的讨论 light userdata。
当然,你也可以使用字符串作为 registry 的 key,只要你保证这些字符串唯一。当你打算允许其他的独立库房问你的数据的时候,字符串型的 key 是非常有用的,因为他们需要知道 key 的名字。对这种情况,没有什么方法可以绝对防止名称冲突,但有一些好的习惯可以采用,比如使用库的名称作为字符串的前缀等类似的方法。类似 lua 或者 lualib 的前缀不是一个好的选择。另一个可选的方法是使用 universal unique identifier(uuid), 很多系统都有专门的程序来产生这种标示符(比如 linux 下的 uuidgen)。一个 uuid 是一 个由本机 IP 地址、时间戳、和一个随机内容组合起来的 128 位的数字(以16 进制的方式书写,用来形成一个字符串),因此它与其他的 uuid 不同是可以保证的。int r = luaL_ref(L, LUA_REGISTRYINDEX);从栈中弹出一个值,以一个新的数字作为 key 将其保存到 registry 中,并返回这个 key。我们将这个 key 称之为 reference。
lua_rawgeti(L, LUA_REGISTRYINDEX, r);最后,我们调用下面的函数释放值和 reference:
luaL_unref(L, LUA_REGISTRYINDEX, r);调用这个之后,luaL_ref 可以再次返回 r 作为一个新的 reference。
luaL_unref(L, LUA_REGISTRYINDEX, LUA_REFNIL);然而
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_REFNIL);像预期的一样,将一个 nil 入栈。
/* forward declaration */ static int counter (lua_State *L); int newCounter (lua_State *L) { lua_pushnumber(L, 0); lua_pushcclosure(L, &counter, 1); return 1; }这里的关键函数是 lua_pushcclosure,她的第二个参数是一个基本函数(例子中卫 counter),第三个参数是 upvalues 的个数(例子中为 1)。在创建新的闭包之前,我们必须将 upvalues 的初始值入栈,在我们的例子中,我们将数字 0 作为唯一的 upvalue 的初 始值入栈。如预期的一样,lua_pushcclosure 将新的闭包放到栈内,因此闭包己经作为 newCounter 的结果被返回。
static int counter (lua_State *L) { double val = lua_tonumber(L, lua_upvalueindex(1)); lua_pushnumber(L, ++val); /* new value */ lua_pushvalue(L, -1); /* duplicate it */ lua_replace(L, lua_upvalueindex(1)); /* update upvalue */ return 1; /* return new value */ }这里的关键函数是 lua_upvalueindex(实际是一个宏),用来产生一个 upvalue 的假 索引。这个假索引除了不在栈中之外,和其他的索引一样。表达式 lua_upvalueindex(1) 函数第一个 upvalue 的索引。因此,在函数 counter 中的 lua_tonumber 获取第一个(仅有 的)upvalue 的当前值,转换为数字型。然后,函数 counter 将新的值++val 入栈,并将这 个值的一个拷贝使用新的值替换 upvalue。最后,返回其他的拷贝。