第一版是為 Lua 5.0 所寫。雖然對於後來的版本來說,大部分內容仍然適用,但有些地方還是有差異。
第四版針對 Lua 5.3,可以在 Amazon 和其他書店買到。
購買本書,您也可以 贊助 Lua 專案


27.2 – 字串處理

當一個 C 函式從 Lua 接收一個字串引數時,它必須遵守兩個規則:存取時不要從堆疊中彈出字串,且永遠不要修改字串。

當一個 C 函式需要建立一個字串回傳給 Lua 時,事情就變得比較困難。現在,C 程式碼必須負責緩衝區配置/解除配置、緩衝區溢位,以及類似的事情。儘管如此,Lua API 還是提供了一些函式來協助這些任務。

標準 API 提供支援兩個最基本的字串操作:擷取子字串和字串串接。要擷取子字串,請記住基本操作 lua_pushlstring 將字串長度作為一個額外的引數。因此,如果您要傳遞一個字串 s 的子字串給 Lua,範圍從位置 ij(包含),您所要做的就是

    lua_pushlstring(L, s+i, j-i+1);
例如,假設您想要一個函式,根據一個給定的分隔符號(一個單一字元)來分割一個字串,並回傳一個包含子字串的表格。例如,呼叫
    split("hi,,there", ",")
應該會回傳表格 {"hi", "", "there"}。我們可以寫一個簡單的實作如下。它不需要額外的緩衝區,而且對於可以處理的字串大小沒有限制。
    static int l_split (lua_State *L) {
      const char *s = luaL_checkstring(L, 1);
      const char *sep = luaL_checkstring(L, 2);
      const char *e;
      int i = 1;
    
      lua_newtable(L);  /* result */
    
      /* repeat for each separator */
      while ((e = strchr(s, *sep)) != NULL) {
        lua_pushlstring(L, s, e-s);  /* push substring */
        lua_rawseti(L, -2, i++);
        s = e + 1;  /* skip separator */
      }
    
      /* push last substring */
      lua_pushstring(L, s);
      lua_rawseti(L, -2, i);
    
      return 1;  /* return the table */
    }

為了串接字串,Lua 在其 API 中提供了一個特定的函式,稱為 lua_concat。它等於 Lua 中的 .. 算子:它會將數字轉換為字串,並在必要時觸發元方法。此外,它可以一次串接兩個以上的字串。呼叫 lua_concat(L, n) 會串接(並彈出)堆疊頂端的 n 個值,並將結果留在頂端。

另一個有用的函式是 lua_pushfstring

    const char *lua_pushfstring (lua_State *L,
                                 const char *fmt, ...);
它有點類似於 C 函數 sprintf,因為它根據格式字串和一些額外參數建立一個字串。但是,與 sprintf 不同的是,您不需要提供一個緩衝區。Lua 會動態地為您建立一個字串,大小會根據需要而定。不用擔心緩衝區溢位之類的問題。函數會將結果字串推入堆疊,並回傳一個指標。目前,此函數只接受指令 %%(代表字元 %´)、%s(代表字串)、%d(代表整數)、%f(代表 Lua 數字,也就是雙精度浮點數)和 %c(接受一個整數並將其格式化為一個字元)。它不接受任何選項(例如寬度或精度)。

當我們只想串接幾個字串時,lua_concatlua_pushfstring 兩個函數都很有用。但是,如果我們需要串接許多字串(或字元),一個一個串接的方法可能會相當沒有效率,正如我們在 第 11.6 節 中所看到的。我們可以使用輔助函式庫提供的緩衝區功能。Auxlib 在兩個層級中實作這些緩衝區。第一個層級類似於 I/O 作業中的緩衝區:它會在一個本機緩衝區中收集小字串(或個別字元),並在緩衝區填滿時將它們傳遞給 Lua(使用 lua_pushlstring)。第二個層級使用 lua_concat 和我們在 第 11.6 節 中看到的堆疊演算法的一種變體,來串接多個緩衝區清除的結果。

為了更詳細地描述 auxlib 中的緩衝區功能,讓我們來看一個簡單的範例。以下程式碼顯示 string.upper 的實作,直接來自檔案 lstrlib.c

    static int str_upper (lua_State *L) {
      size_t l;
      size_t i;
      luaL_Buffer b;
      const char *s = luaL_checklstr(L, 1, &l);
      luaL_buffinit(L, &b);
      for (i=0; i<l; i++)
        luaL_putchar(&b, toupper((unsigned char)(s[i])));
      luaL_pushresult(&b);
      return 1;
    }
使用 auxlib 中的緩衝區的第一個步驟是宣告一個型別為 luaL_Buffer 的變數,然後使用 luaL_buffinit 呼叫來初始化它。初始化後,緩衝區會保留狀態 L 的一份副本,因此我們在呼叫其他用於操作緩衝區的函數時不需要傳遞它。巨集 luaL_putchar 會將一個單一字元放入緩衝區中。Auxlib 也提供 luaL_addlstring,用於將一個具有明確長度的字串放入緩衝區中,以及 luaL_addstring,用於放入一個以零終止的字串。最後,luaL_pushresult 會清除緩衝區,並將最終字串留在堆疊的頂端。這些函數的原型如下
    void luaL_buffinit (lua_State *L, luaL_Buffer *B);
    void luaL_putchar (luaL_Buffer *B, char c);
    void luaL_addlstring (luaL_Buffer *B, const char *s,
                                          size_t l);
    void luaL_addstring (luaL_Buffer *B, const char *s);
    void luaL_pushresult (luaL_Buffer *B);

使用這些函數,我們不必擔心緩衝區分配、溢位和其他此類細節。正如我們所看到的,串接演算法相當有效率。str_upper 函數可以毫無問題地處理大型字串(超過 1 MB)。

當您使用 auxlib 緩衝區時,您必須注意一個細節。當您將內容放入緩衝區時,它會將一些中間結果保存在 Lua 堆疊中。因此,您不能假設堆疊頂端會保留在您開始使用緩衝區之前的位置。此外,儘管您可以在使用緩衝區時將堆疊用於其他任務(甚至建立另一個緩衝區),但每次存取緩衝區時,這些用途的 push/pop 計數都必須平衡。有一個明顯的情況下,這個限制過於嚴格,即當您要將 Lua 傳回的字串放入緩衝區時。在這種情況下,您不能在將字串新增到緩衝區之前將其彈出,因為您不應該在從堆疊中彈出字串後使用 Lua 的字串;但您也不能在彈出字串之前將其新增到緩衝區,因為堆疊將處於錯誤的層級。換句話說,您不能執行類似這樣的操作

    luaL_addstring(&b, lua_tostring(L, 1));   /* BAD CODE */
由於這是一個常見的情況,auxlib 提供了一個特殊函式,將堆疊頂端的數值新增到緩衝區中
    void luaL_addvalue (luaL_Buffer *B);
當然,如果頂端的數值不是字串或數字,呼叫這個函式會是一個錯誤。