Lua C++表迭代

5

我有些困惑lua_next的工作原理。用户定义了一个表:

a={["a1"]=20,["a2"]=30}

我想用C++代码打印这个表格:
inline int lua_print(lua_State* L)
{
wxString wxReturnStr=wxEmptyString;
wxString tempString=wxEmptyString;
int nargs = lua_gettop(L);

for (int i=1; i <= nargs; i++)
{
    int type = lua_type(L, i);
    switch (type)
    {

    case LUA_TNIL:
        break;

    case LUA_TBOOLEAN:
        tempString<<(lua_toboolean(L, i) ? "true" : "false");
        break;

    case LUA_TNUMBER:
        tempString<<lua_tonumber(L, i);
        break;

      case LUA_TSTRING:
          tempString<<lua_tostring(L, i);
        break;
      case LUA_TTABLE:
      {
          lua_pushnil(L);
          while(lua_next(L,-2))
          {
                const char* key=lua_tostring(L,-2);
                double val=lua_tonumber(L,-1);
                lua_pop(L,1);
                tempString<<key<<"="<<val<<"\t";
          }

          break;
      }

      default:
          tempString<<lua_typename(L, type);
        break;
    }

    wxReturnStr=wxReturnStr+tempString+"\n";
    tempString=wxEmptyString;

}

lua_pop(L,nargs);

当我从Lua中调用时,这段代码运行得非常好:

print(a) -- Works well

然而,假设我在Lua中有一个表格,如下所示:
b={["b1"]=10, ["b2"]=15}

如果我这样调用代码:

print(a,b) -- Twice prints only contents of b 

我理解lua_next的工作方式如下图所示:enter image description here[版本#1]

问题出在哪里?


你是如何给 ab 赋值的? - Kyle
2个回答

3

这个 bug 在 lua_next(L, -2) 行,因为 -2 指的是栈顶减一,而这里恰好是 print 的最后一个参数。

请使用 lua_next(L, i) 代替。

更新:在开发阶段移动代码时,Lua 栈索引会受到浮点数的影响,因此一般建议在获取/推送/考虑值后立即使用小整数 int t = lua_gettop(L) 固定索引,并使用该 t 而不是 -n(尽管这种情况似乎是一种按键错误)。


你提供的建议 lua_next(L,i) 是行得通的。但是,我现在有些困惑了... 当我使用 lua_next(L,1) 时,即使 i=1,它也会引用栈底,这是我的理解。然而,当我使用 lua_pop(L,1) 时,它则从栈顶弹出。我不确定它是如何工作的... - macroland
@macroland 一些函数需要以堆栈索引(1..top / -top..-1)作为参数,而其他函数则需要计数。lua_next()、lua_tostring()和lua_getfield()需要堆栈索引。lua_pop()和lua_concat()需要在堆栈顶部删除/连接的值的数量。你不能从堆栈中间弹出,这就是为什么有lua_remove()的存在。如果有疑问,请查阅特定函数的文档。 - user3125367
谢谢!当我们对i=1使用 lua_next(L,i) 时,这是指堆栈底部,也就是绝对索引为1的元素。然而,当我们从堆栈中弹出元素时,唯一确定的方式是从堆栈顶部弹出。现在假设我在堆栈上有两个表(表a在索引1处,表b在索引2处),当我们运行lua_next(L,1)时,代码是在引用表a。这会“获取”来自表a的值,并将其推送到堆栈上。现在使用idx=-1表示值,使用idx=-2表示键是有意义的。我的理解是,lua_next内部会执行某些操作以将表a(索引1)从堆栈中移除。 - macroland
我的理解是:https://drive.google.com/file/d/0B6oCKmT1bPAyYkRveFZGZnZnT2M/view - macroland
lua_next() 不会修改或删除表。它弹出键并从哈希表桶中推入键值对,该键值对在该键旁边(如果键是最后一个,则为无)。nil 被视为“最小”的键,因此您可以从 nil 开始遍历所有表 kv-pairs。每次迭代弹出值都会将最后一个键留在顶部以供下一个 lua_next() 调用,有效且自然地循环遍历所有键。请阅读文档,没有比所描述的更多或更少的行为。您的代码移动到 b 是因为增加了 i 的值,而不是删除索引 1 处的值。 - user3125367
最后一个链接中的图片没问题,顺便说一下,(唯一的注意事项是)堆栈通常是从上往下绘制的,即向下增长。 - user3125367

1

在处理完表格后,您忘记了使用lua_pop。

 lua_pushnil(L);
      while(lua_next(L,-2))
      {
            const char* key=lua_tostring(L,-2);
            double val=lua_tonumber(L,-1);
            lua_pop(L,1);
            tempString<<key<<"="<<val<<"\t";
      }
 lua_pop(L, 1); // THE FIX, pops the nil on the stack used to process the table

这意味着,在堆栈上留下了额外的nil,因此在第二次迭代中,
case LUA_TNIL:
   break;

仅打印空白。

关于您对堆栈的图形表示。每个图像下面的命令表示在调用该命令之后的状态。因此,最后一个图像在堆栈上缺少[Key = a2]项。


OPs 的 while 循环不会在堆栈上留下 nil,因为 lua_next 消耗了初始的 nil 并在返回0时不会推入任何内容。 - user3125367
你建议在块末尾使用 lua_pop(L,1),但我在发布这篇文章之前就已经尝试过了,它并不起作用。例如,对于 print(a,b),它只打印第一个参数,第二个参数在输出之前就被弹出了。 - macroland

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接