此第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于后续版本,但有一些差异。
第四版针对 Lua 5.3,可在 亚马逊 和其他书店购买。
购买本书,您还可以帮助 支持 Lua 项目。
用 Lua 编程 | ||
第四部分。C API 第 27 章。编写 C 函数的技术 |
当 C 函数从 Lua 接收字符串参数时,它必须遵守两个规则:在访问字符串时不将其从堆栈中弹出,并且绝不修改字符串。
当 C 函数需要创建字符串返回给 Lua 时,事情变得更加困难。现在,C 代码需要负责缓冲区分配/释放、缓冲区溢出等。不过,Lua API 提供了一些函数来帮助完成这些任务。
标准 API 为两个最基本的字符串操作提供支持:子字符串提取和字符串连接。要提取子字符串,请记住基本操作 lua_pushlstring
将字符串长度作为额外参数获取。因此,如果您想将字符串 s
的子字符串(从位置 i
到 j
(包括))传递给 Lua,您只需
lua_pushlstring(L, s+i, j-i+1);例如,假设您想要一个函数,根据给定的分隔符(单个字符)分割字符串,并返回一个包含子字符串的表。例如,调用
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 中的 ..
运算符:它将数字转换为字符串,并在必要时触发元方法。此外,它可以一次连接两个以上的字符串。调用 lua_concat(L, n)
将连接(并弹出)堆栈顶部的 n
个值,并将结果留在顶部。
另一个有用的函数是 lua_pushfstring
const char *lua_pushfstring (lua_State *L, const char *fmt, ...);它有点类似于 C 函数
sprintf
,因为它根据格式字符串和一些额外参数创建字符串。但是,与 sprintf
不同,您无需提供缓冲区。Lua 会根据需要动态创建字符串。无需担心缓冲区溢出等问题。该函数将结果字符串压入堆栈并返回指向它的指针。目前,此函数仅接受指令 %%
(用于字符 %
´)、%s
(用于字符串)、%d
(用于整数)、%f
(用于 Lua 数字,即双精度)和 %c
(接受整数并将其格式化为字符)。它不接受任何选项(例如宽度或精度)。
当我们只想连接几个字符串时,lua_concat
和 lua_pushfstring
都很有用。但是,如果我们需要连接多个字符串(或字符),那么逐个连接的方法效率很低,正如我们在 第 11.6 节 中所看到的。相反,我们可以使用辅助库提供的缓冲区工具。Auxlib 在两个级别实现这些缓冲区。第一级类似于 I/O 操作中的缓冲区:它在一个本地缓冲区中收集小字符串(或单个字符),并在缓冲区填满时将它们传递给 Lua(使用 lua_pushlstring
)。第二级使用 lua_concat
和我们在 第 11.6 节 中看到的堆栈算法的一个变体,来连接多个缓冲区刷新结果。
为了更详细地描述 auxlib 中的缓冲区工具,让我们看一个简单的使用示例。下面的代码展示了 string.upper
的实现,直接来自文件 lstrlib.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 中的缓冲区的第一个步骤是声明一个类型为
luaL_Buffer
的变量,然后使用 luaL_buffinit
调用对其进行初始化。初始化后,缓冲区会保留状态 L
的副本,因此在调用其他操作缓冲区的函数时,我们不需要传递它。宏 luaL_putchar
将单个字符放入缓冲区。Auxlib 还提供了 luaL_addlstring
,将具有显式长度的字符串放入缓冲区,以及 luaL_addstring
,将以零结尾的字符串放入缓冲区。最后,luaL_pushresult
刷新缓冲区,并将最终字符串留在堆栈顶部。这些函数的原型如下
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);
使用这些函数,我们不必担心缓冲区分配、溢出和其他此类细节。正如我们所看到的,连接算法非常高效。str_upper
函数可以毫无问题地处理大字符串(超过 1 MB)。
当您使用 auxlib 缓冲区时,您必须注意一个细节。当您将内容放入缓冲区时,它会在 Lua 堆栈中保留一些中间结果。因此,您不能假设堆栈顶部会保留在您开始使用缓冲区之前的状态。此外,尽管您可以在使用缓冲区时将堆栈用于其他任务(甚至构建另一个缓冲区),但每次访问缓冲区时,这些用途的 push/pop 计数都必须平衡。有一个明显的情况,这种限制过于严格,即当您想要将 Lua 返回的字符串放入缓冲区时。在这种情况下,您不能在将字符串添加到缓冲区之前将其弹出,因为您永远不应该在从堆栈中弹出字符串后使用它;但您也不能在弹出字符串之前将其添加到缓冲区,因为堆栈将处于错误的级别。换句话说,您不能执行类似这样的操作
luaL_addstring(&b, lua_tostring(L, 1)); /* BAD CODE */由于这是一个常见的情况,auxlib 提供了一个特殊函数,用于将栈顶的值添加到缓冲区中
void luaL_addvalue (luaL_Buffer *B);当然,如果栈顶的值不是字符串或数字,则调用此函数将出错。
版权所有 © 2003–2004 Roberto Ierusalimschy。保留所有权利。 |