27.2 字符串处理

C函数接受一个来自lua的字符串作为参数时,有两个规则必须遵守:当字符串正在被访问的时候不要将其出栈;永远不要修改字符串。

C函数需要创建一个字符串返回给lua的时候,情况变得更加复杂。这样需要由C代码来负责缓冲区的分配和释放,负责处理缓冲溢出等情况。然而,Lua API提供了一些函数来帮助我们处理这些问题。

标准API提供了对两种基本字符串操作的支持:子串截取和字符串连接。记住,lua_pushlstring可以接受一个额外的参数,字符串的长度来实现字符串的截取,所以,如果你想将字符串sij位置(包含ij)的子串传递给lua,只需要:

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个值,并将最终结果放到栈顶。

另一个有用的函数是lua_pushfstring

const char *lua_pushfstring (lua_State *L,

                                const char *fmt, ...);

这个函数某种程度上类似于C语言中的sprintf,根据格式串fmt的要求创建一个新的字符串。与sprintf不同的是,你不需要提供一个字符串缓冲数组,Lua为你动态的创建新的字符串,按他实际需要的大小。也不需要担心缓冲区溢出等问题。这个函数会将结果字符串放到栈内,并返回一个指向这个结果串的指针。当前,这个函数只支持下列几个指示符: %%(表示字符 '%')、%s(用来格式化字符串)、%d(格式化整数)、%f(格式化Lua数字,即 doubles)和 %c(接受一个数字并将其作为字符),不支持宽度和精度等选项。

当我们打算连接少量的字符串的时候,lua_concatlua_pushfstring是很有用的,然而,如果我们需要连接大量的字符串(或者字符),这种一个一个的连接方式效率是很低的,正如我们在11.6节看到的那样。我们可以使用辅助库提供的buffer相关函数来解决这个问题。Auxlib在两个层次上实现了这些buffer。第一个层次类似于I/O操作的buffers:集中所有的字符串(或者但个字符)放到一个本地buffer中,当本地buffer满的时候将其传递给Lua(使用lua_pushlstring)。第二个层次使用lua_concat和我们在11.6节中看到的那个栈算法的变体,来连接多个buffer的结果。

为了更详细地描述Auxlib中的buffer的使用,我们来看一个简单的应用。下面这段代码显示了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;

}

使用Auxlibbuffer的第一步是使用类型luaL_Buffer声明一个变量,然后调用luaL_buffinit初始化这个变量。初始化之后,buffer保留了一份状态L的拷贝,因此当我们调用其他操作buffer的函数的时候不需要传递L。宏luaL_putchar将一个单个字符放入bufferAuxlib也提供了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)。

当你使用auxlib中的buffer时,不必担心一点细节问题。你只要将东西放入buffer,程序会自动在Lua栈中保存中间结果。所以,你不要认为栈顶会保持你开始使用buffer的那个状态。另外,虽然你可以在使用buffer的时候,将栈用作其他用途,但每次你访问buffer的时候,这些其他用途的操作进行的push/pop操作必须保持平衡[8]。有一种情况,即你打算将从Lua返回的字符串放入buffer时,这种情况下,这些限制有些过于严格。这种情况下,在将字符串放入buffer之前,不能将字符串出栈,因为一旦你从栈中将来自于Lua的字符串移出,你就永远不能使用这个字符串。同时,在将一个字符串出栈之前,你也不能够将其放入buffer,因为那样会将栈置于错误的层次(because then the stack would be in the wrong level)。换句话说你不能做类似下面的事情:

luaL_addstring(&b, lua_tostring(L, 1));   /* BAD CODE */

(译者:上面正好构成了一对矛盾),由于这种情况是很常见的,auxlib提供了特殊的函数来将位于栈顶的值放入buffer

void luaL_addvalue (luaL_Buffer *B);

当然,如果位于栈顶的值不是字符串或者数字的话,调用这个函数将会出错。


相关链接:
lua程序设计目录 - 中国lua开发者 - lua论坛