第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于更高版本,但有一些差异。
第四版针对 Lua 5.3,可在 亚马逊 和其他书店购买。
购买本书,您还可以帮助 支持 Lua 项目


27.3.3 – 上值

注册表实现全局值,而上值机制实现 C 静态变量的等价项,这些变量仅在特定函数内可见。每次在 Lua 中创建新的 C 函数时,都可以将任意数量的上值与其关联;每个上值可以保存一个 Lua 值。稍后,当调用该函数时,它可以使用伪索引自由访问其任何上值。

我们将 C 函数与其上值的这种关联称为闭包。请记住,在 Lua 代码中,闭包是一个使用来自外部函数的局部变量的函数。C 闭包是对 Lua 闭包的 C 近似。关于闭包的一个有趣的事实是,可以使用相同的函数代码创建不同的闭包,但使用不同的上值。

为了查看一个简单的示例,让我们在 C 中创建一个 newCounter 函数。(我们已经在 Lua 中定义了这个相同的函数,请参见 第 6.1 节。)此函数是一个工厂函数:每次调用它时,它都会返回一个新的计数器函数。虽然所有计数器共享相同的 C 代码,但每个计数器都保持自己的独立计数器。工厂函数如下所示

    /* forward declaration */
    static int counter (lua_State *L);
    
    int newCounter (lua_State *L) {
      lua_pushnumber(L, 0);
      lua_pushcclosure(L, &counter, 1);
      return 1;
    }
此处的关键函数是 lua_pushcclosure,它创建一个新的闭包。它的第二个参数是基本函数(示例中的 counter),第三个参数是上值的数量(示例中的 1)。在创建新的闭包之前,我们必须将上值的初始值压入堆栈。在我们的示例中,我们将数字 0 作为单个上值的初始值进行推送。正如预期的那样,lua_pushcclosure 将新的闭包留在堆栈上,因此闭包已准备好作为 newCounter 的结果返回。

现在,让我们看看 counter 的定义

    static int counter (lua_State *L) {
      double val = lua_tonumber(L, lua_upvalueindex(1));
      lua_pushnumber(L, ++val);  /* new value */
      lua_pushvalue(L, -1);  /* duplicate it */
      lua_replace(L, lua_upvalueindex(1));  /* update upvalue */
      return 1;  /* return new value */
    }
在此,关键函数是 lua_upvalueindex(实际上是一个宏),它生成一个 upvalue 的伪索引。同样,这个伪索引就像任何栈索引,除了它不在栈中。表达式 lua_upvalueindex(1) 指示函数的第一个 upvalue 的索引。因此,函数 counter 中的 lua_tonumber 将第一个(也是唯一一个)upvalue 的当前值作为数字检索。然后,函数 counter 推入新值 ++val,复制它,并使用其中一个副本用新值替换 upvalue。最后,它将另一个副本作为其返回值返回。

与 Lua 闭包不同,C 闭包无法共享 upvalue:每个闭包都有自己独立的集合。但是,我们可以设置不同函数的 upvalue 以引用一个公共表,这样该表就成为这些函数可以共享数据的公共场所。