使用C/C++语言遍历lua的table表

陪她去流浪 桃子 2016年01月17日 编辑 阅读次数:4803

在宿主语言中操作lua的表跟在lua语言中的操作实际上很相似。

在lua中我们多数时候是调用 pairs/ipairs 来迭代,不会直接使用 next 函数。但在宿主语言中只有 next,所以有必要讲下 lua 中 next 的用法:

  1. 置 local k, v = nil,即先用 nil 调用 next 开始迭代;
  2. 调用 k,v = next(table, k)
  3. 如果 k ~= nil,则代表 k 和 v 是一对有效值;否则转到结束;
  4. 使用 k, v;
  5. 回到第2步,用此时的 k 再次调用 k, v = next(table, k) 获取下一组值;
  6. 结束。

写成代码有点像下面这样:

local a = {
  key1 = "value1",
  key2 = "value2",
}

local k, v
while true do
  k, v = next(a, k)
  if not k then
    break
  end
  print(k .. ': ' .. v)
end

在宿主语言(这里的C/C++)中操作也非常相似:

#include <cstdio>

#include <dll/lua.hpp>


static const char* get_code() {
    return "return {"
"  key1 = 'value1',"
"  key2 = 'value2',"
"}";
}

static void iterate(lua_State* L, int index) {
    // 为了使被操作的表在栈顶,我们需要作一些操作来确保
    bool ontop = index == -1;

    // 如果不在栈顶上,就压入此表的一份引用到栈顶
    if(!ontop)
        lua_pushvalue(L, index);

    // 现在的栈是这样的:-1 => table

    // 好了,现在表已经在栈顶上了,像前面操作 next 的方式一样操作
    // 1. 先压入 nil 调用 next 开始迭代
    lua_pushnil(L);
    // 现在的栈是这样的:-1 => nil, -2 => table

    // 2. 现在循环调用 next 来获取下一组键值
    // 第1个参数待查询的键,第2个参数是表的索引
    // 如果没有可迭代的元素了,lua_next 会返回 0
    while(lua_next(L, -2)) {
        // 现在的栈是这样的:-1 => value, -2 => key, -3 => table
        // 3. 值已经拿到了,可以使用了。但还有一点需要注意:
        //    如果 key 不是字符串的话,不能使用 lua_tostring,原因请看
        //    官方文档:http://www.lua.org/manual/5.3/manual.html#lua_next
        //    如果真的想用 lua_tostring 的话,可以先压入一份 key 的拷贝。
        // 我这里为了简单起见,使用了字符串,就不需要再考虑了。
        const char* key = lua_tostring(L, -2);
        const char* val = lua_tostring(L, -1);
        printf("%s => %s\n", key, val);

        // 现在完后,干掉 value 保留 key,为下一次迭代作准备
        lua_pop(L, 1);
        // 现在的栈是这样的:-1 => key, -2 => table
        // 已经回到开始遍历时的栈帧了,可以下一次遍历了
    }

    // lua_next 不止会返回0,而且还会帮我们把最后一个 key 给弹出去,只保留最初那个表
    // 现在的栈是这样的:-1 => table

    // 好了,让一切回到最初
    if(!ontop)
        lua_pop(L, 1);
}

int main() {
    int rc;
    lua_State* L;
    
    L = luaL_newstate();
    luaL_openlibs(L);

    // 编译代码,示例代码只是简地返回一个表
    // 若编译成功,以匿名函数的形式返回此代码的执行码
    rc = luaL_loadstring(L, get_code()) == LUA_OK;

    // 所以这里调用 pcall 来调用此匿名函数
    // 若成功,栈顶上是上面我们返回的具有两个键值的表
    rc = rc && lua_pcall(L, 0, 1, NULL) == LUA_OK;

    if(rc) {
        // 这里我把迭代函数给独立出去了,有一些原因:
        //   在调用 next 函数迭代表的键值时,需要该表在栈顶(索引为-1)才行。
        //   但这里的栈结构很简单,只有一个元素,我们即将操作的表,但很多时候,
        //   表不一定是在栈顶。所以得写一个更通用的算法。
        iterate(L, -1);
    }

    lua_close(L);
    return 0;
}

参考

标签:lua