第一版是針對 Lua 5.0 編寫的。雖然在很大程度上仍然適用於後續版本,但有些地方有所不同。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買書籍,您也協助 支援 Lua 專案


27.3.3 – Upvalue

雖然註冊表實作全域變數,但upvalue 機制實作等同於 C 靜態變數,只能在特定函式內部可見。每次在 Lua 中建立新的 C 函式時,您可以將任意數量的 upvalue 與其關聯;每個 upvalue 可以儲存單一 Lua 值。稍後呼叫函式時,它可以使用偽索引自由存取任何 upvalue。

我們將 C 函式與其 upvalue 的關聯稱為閉包。請記住,在 Lua 程式碼中,閉包是使用外部函式的區域變數的函式。C 閉包是 Lua 閉包的 C 近似值。關於閉包的有趣事實之一是,您可以使用相同的函式程式碼建立不同的閉包,但具有不同的 upvalue。

為了查看簡單範例,讓我們在 C 中建立一個 newCounter 函式。(我們已經在 第 6.1 節 中的 Lua 中定義了這個函式。)這個函式是工廠函式:每次呼叫時,它會傳回新的計數器函式。雖然所有計數器共用相同的 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),而第三個參數則是 upvalue 的數量(範例中的 1)。在建立新的閉包之前,我們必須將其 upvalue 的初始值推入堆疊中。在我們的範例中,我們將數字 0 推入堆疊中,作為單一 upvalue 的初始值。正如預期,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,使其參考同一張表,讓這張表成為這些函數可以共用資料的共用位置。