欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Lua教程(五):C/C++操作Lua数组和字符串示例

程序员文章站 2022-03-07 10:24:37
本文将介绍如何在c/c++里面操作lua的数组和字符串类型,同时还会介绍如何在c/c++函数里面存储lua状态(registry和upvalue),而registry在使用...

本文将介绍如何在c/c++里面操作lua的数组和字符串类型,同时还会介绍如何在c/c++函数里面存储lua状态(registry和upvalue),而registry在使用c/c++自定义类型时非常有用,可以方便地为userdata指定metatable。

c/c++操作lua数组

lua数组overview

在lua里面,数组只不过是key为整数的table而已。比如一个table为array = {12,”hello”, “world”},它是一个数组,可以用下面的代码来访问它:

复制代码 代码如下:

print(array[1])  --这里会输出array的第一个元素12。
print(array[3]) --这里会输出array的第三个元素world

需要注意的一点就是:lua的数组的下标是从1开始的。如果你使用下面的语句则会输出nil值:

复制代码 代码如下:

print(array[0])  --输出nil
print(array["1"])  --输出nil(想想和array[1]的区别:一个是integer作为key,一个是字符串做为key)

通用table操作方法

之前我们在中介绍了如何传递table给lua,以及在中介绍了如何访问table的数据。因为数组也是table,所以我们可以用同样的方式来读取数组。

读取数组

假设我们的lua table为array = {“hello”, 1, “world”, 23.2},那么我们可以用下列函数来访问它:

复制代码 代码如下:

void readluaarray(lua_state *l)
{
    lua_settop(l,0); //这样确保我们的array是放在当前栈的栈顶。
    lua_getglobal(l, "array");
    //如果前面不调用lua_settop(l,0),那我们必须要使用lual_len(l,-1)
    int n = lual_len(l, 1);   //lual_len可以获得table的元素个数
    for (int i = 1; i <= n; ++i) {
        lua_pushnumber(l, i);  //往栈里面压入i
        lua_gettable(l, -2);  //读取table[i],table位于-2的位置。
        //lua_rawget(l, -2);  //lua_gettable也可以用lua_rawget来替换
        cout<<lua_tostring(l, -1)<<endl;
        lua_pop(l, 1);
    }
}

最后输出的结果为:

复制代码 代码如下:

"hello", 1, "world", 23.2

修改数组

现在我们如果想要修改这个数组,把每一个数组的元素都变成”hehe[i]”(i = 1-n),我们看看怎么做。

复制代码 代码如下:

int writeluaarray(lua_state *l)
{
    lua_settop(l, 0);
    lua_getglobal(l, "array");
    //确保第一个函数一个要是一个table
    lual_checktype(l, 1, lua_ttable);
    int n = lual_len(l,1);
    for (int i = 1; i <= n; ++i) {
        lua_pushnumber(l, i);
        char buf[256];
        sprintf(buf, "hehe%d", i);
        lua_pushstring(l, buf);
//        lua_settable(l, -3);
        lua_rawset(l, -3);
    }
    return 0;
}
}

注意这里的lua_rawset和lua_settable是等价的,只不过lua_rawset速度更快。 最后,我们在加载完lua脚本以后调用这两个函数:

复制代码 代码如下:

writeluaarray(l);
readluaarray(l);

输出结果为:

复制代码 代码如下:

readluaarray: hehe1
readluaarray: hehe2
readluaarray: hehe3
readluaarray: hehe4

专门的数组操作方法

因为数组一般在程序语言里面都会被特殊对待,lua也不例外,它的c api还提供另外一种更方便高效地方法来存取数组的元素。

复制代码 代码如下:

 void lua_rawgeti (lua_state *l, int index, int key);
 void lua_rawseti (lua_state *l, int index, int key);

这两个函数后面两个参数的意思分别是:index(table在栈中的索引),key(table中数组的索引,下标从1开始) 接下来,我会通过改造上面的示例来演示这两个api的用法。

读取数组

因为lua_rawgeti(l,t,key)等价于:

复制代码 代码如下:

 lua_pushnumber(l, key);
 lua_rawget(l, t);

因此,我们的读取代码可以改写成下面这样:

复制代码 代码如下:

void readluaarray(lua_state *l)
{
    lua_getglobal(l, "array");
    int n = lual_len(l, -1);
    for (int i = 1; i <= n; ++i) {
        lua_rawgeti(l, 1, i);
        cout<<"readluaarray: "<<lua_tostring(l, -1)<<endl;
        lua_pop(l, 1);
    }
}

修改数组

同理,lua_rawset(l,t,key)等价于

复制代码 代码如下:

lua_pushnumber(l,key); //此时的栈 table->value->key
lua_insert(l,-2);  //调用完后的栈: table->key->value (table[key]=value)
lua_rawset(l,t);

相应的修改数组的代码可以修改为:

复制代码 代码如下:

int writeluaarray(lua_state *l)
{
    lua_settop(l, 0);
    lua_getglobal(l, "array");
    //确保第一个函数一个要是一个table
    lual_checktype(l, 1, lua_ttable);
    int n = lual_len(l,1);
    for (int i = 1; i <= n; ++i) {
        char buf[256];
        sprintf(buf, "hehe%d", i);
        lua_pushstring(l, buf);
        lua_rawseti(l, 1, i);
    }
    return 0;
}

c/c++操作lua字符串

基本字符串操作

lua c api操作字符串主要包含两个操作:求子串(lua_pushlstring)和字符串拼接(lua_concat). 例如,我们求一个字符串s的子串[i,j],它可以表示为:

复制代码 代码如下:

lua_pushlstring(l, s + i, j - i + 1);

而lua_concat(l,n)则可以把当前栈顶的n个元素转换成字符串并拼接起来,最后把结果压入栈顶。 比如,我们想定义一个函数mycontact(…,n)可以把n个字符串拼接起来,n表示字符串的个数,那么我们的代码可以写成这样:
复制代码 代码如下:

static int l_mycontact(lua_state* l){
    lual_checktype(l, -1, lua_tnumber);
    int n = lua_tonumber(l, -1);
    lua_pop(l, 1);
    lua_concat(l, n);
    return 1;
}

然后,我们需要注册此函数到libs中去,最后在lua里面调用此函数:

复制代码 代码如下:

print(mylib.mycontact("zilong","shanren"," meng meng"," da",4))

输出结果为:

复制代码 代码如下:

zilongshanren meng meng da

格式化输出

当我们想要往lua里面写入一个格式化字符串时,可以使用函数

复制代码 代码如下:

const char *lua_pushfstring (lua_state *l, const char *fmt, ...);

另外,我们还可以使用lual_buffer,下面是pil书中的示例,把lua字符串转换成大写:

复制代码 代码如下:

 static int str_upper (lua_state *l) {
     size_t l;
     size_t i;
     lual_buffer b;
     const char *s = lual_checklstring(l, 1, &l);  //从lua栈中取出字符串
     char *p = lual_buffinitsize(l, &b, l); //分配一块与取出字符串同样大小的缓冲区
     for (i = 0; i < l; i++)
       p[i] = toupper(uchar(s[i]));
     lual_pushresultsize(&b, l);  //把缓冲区结果转换为字符串
     return 1;
}

更多的lua buffer操作函数如下:

复制代码 代码如下:

 void lual_buffinit   (lua_state *l, lual_buffer *b);
 void lual_addvalue   (lual_buffer *b);
 void lual_addlstring (lual_buffer *b, const char *s, size_t l);
 void lual_addstring  (lual_buffer *b, const char *s);
 void lual_addchar    (lual_buffer *b, char c);
 void lual_pushresult (lual_buffer *b);

关于每一个函数的用法和每一个参数的含义,大家可以去lua的reference manual上去查看,本文就不赘述了。

存储lua状态

在c函数里面,当我们需要保存函数里面的一些状态的时候,我们一般采用全局变量或者静态变量的方式。但是,如果在与lua交互时,这两种方法都不可取。 原因有二: 1. c变量很难存储各种各样的lua变量。 2. 当存在多个lua栈的时候,就不生效了。 在lua里面有两种方法来存在函数内的non-local数据:registry和upvalue.

registry方式

register是一个lua的全局table,只有在lua的c api里面可以访问这个table。它可以用来存储多个lua模块之间的数据。 访问register的方式一般为:

复制代码 代码如下:

 lua_getfield(l, lua_registryindex, "key");

我们需要提供一个lua_registryindex的“伪索引”来标识它在lua栈中的位置。我们在操作这个table的时候,最好是使用字符串做为key,而不要使用数字来做为key。关于registry更为实际的用法,我们会在下一篇文章中讨论。

upvalue方式

upvalue主要用来存储模块或者函数内部的一些私有的数据,它与c语言的静态变量有点类似。具体的用法可以参考pil