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


25.1 – 表格處理

讓我們採用這種態度:現在,我們也想要設定一個背景顏色給視窗。我們假設最終的顏色規格由三個數字組成,每個數字都是 RGB 中的一個顏色元件。在 C 中,這些數字通常是在某個範圍內的整數,例如 [0,255]。在 Lua 中,由於所有數字都是實數,我們可以使用更自然的範圍 [0,1]

一個天真的做法是要求使用者在不同的全域變數中設定每個元件

    -- configuration file for program `pp'
    width = 200
    height = 300
    background_red = 0.30
    background_green = 0.10
    background_blue = 0
這種做法有兩個缺點:它太過冗長(實際的程式可能需要數十種不同的顏色,用於視窗背景、視窗前景、選單背景等);而且沒有辦法預先定義常見的顏色,因此使用者後來只能寫類似 background = WHITE 之類的內容。為了避免這些缺點,我們將使用一個表格來表示一個顏色
    background = {r=0.30, g=0.10, b=0}
使用表格讓腳本有更明確的結構;現在使用者(或應用程式)可以輕鬆地在設定檔中預先定義顏色,以便稍後使用
    BLUE = {r=0, g=0, b=1}
    ...
    background = BLUE
若要在 C 中取得這些值,我們可以執行下列動作
    lua_getglobal(L, "background");
    if (!lua_istable(L, -1))
      error(L, "`background' is not a valid color table");
    
    red = getfield("r");
    green = getfield("g");
    blue = getfield("b");
和往常一樣,我們首先取得全域變數 background 的值,並確保它是一個表格。接下來,我們使用 getfield 取得每個顏色元件。這個函式不是 API 的一部分;我們必須自行定義,如下所示
    #define MAX_COLOR       255
    
    /* assume that table is on the stack top */
    int getfield (const char *key) {
      int result;
      lua_pushstring(L, key);
      lua_gettable(L, -2);  /* get background[key] */
      if (!lua_isnumber(L, -1))
        error(L, "invalid component in background color");
      result = (int)lua_tonumber(L, -1) * MAX_COLOR;
      lua_pop(L, 1);  /* remove number */
      return result;
    }
我們再次面臨多型性的問題:getfield 函式可能有很多版本,它們的關鍵類型、值類型、錯誤處理等各不相同。Lua API 提供一個單一函式 lua_gettable。它接收表格在堆疊中的位置,從堆疊中彈出關鍵字,並推入對應的值。我們私有的 getfield 假設表格在堆疊的最上方;因此,在推入關鍵字(lua_pushstring)後,表格將位於索引 -2。在傳回之前,getfield 會從堆疊中彈出已擷取的值,讓堆疊保持在呼叫前的相同層級。

我們將進一步擴充我們的範例,並為使用者加入顏色名稱。使用者仍然可以使用顏色表格,但也可以對較常見的顏色使用預先定義的名稱。若要實作這個功能,我們需要在 C 應用程式中有一個顏色表格

    struct ColorTable {
      char *name;
      unsigned char red, green, blue;
    } colortable[] = {
      {"WHITE",   MAX_COLOR, MAX_COLOR, MAX_COLOR},
      {"RED",     MAX_COLOR,   0,   0},
      {"GREEN",     0, MAX_COLOR,   0},
      {"BLUE",      0,   0, MAX_COLOR},
      {"BLACK",     0, 0, 0},
      ...
      {NULL,        0, 0, 0}  /* sentinel */
    };

我們的實作將使用顏色表格建立具有顏色名稱的全域變數,並初始化這些變數。結果與使用者在腳本中加入下列幾行內容相同

    WHITE = {r=1, g=1, b=1}
    RED   = {r=1, g=0, b=0}
    ...
這些使用者定義的顏色與應用程式定義的顏色唯一的不同點在於,應用程式在執行使用者腳本之前會在 C 中定義這些顏色。

若要設定表格欄位,我們定義一個輔助函式 setfield;它將索引和欄位值推入堆疊,然後呼叫 lua_settable

    /* assume that table is at the top */
    void setfield (const char *index, int value) {
      lua_pushstring(L, index);
      lua_pushnumber(L, (double)value/MAX_COLOR);
      lua_settable(L, -3);
    }
與其他 API 函式一樣,lua_settable 可用於許多不同的類型,因此它會從堆疊中取得所有運算元。它接收表格索引作為引數,並彈出金鑰和值。setfield 函式假設在呼叫之前,表格位於堆疊頂端 (索引 -1);在推入索引和值後,表格將位於索引 -3

setcolor 函式定義單一顏色。它必須建立一個表格,設定適當的欄位,並將此表格指定給對應的全球變數

    void setcolor (struct ColorTable *ct) {
      lua_newtable(L);               /* creates a table */
      setfield("r", ct->red);        /* table.r = ct->r */
      setfield("g", ct->green);      /* table.g = ct->g */
      setfield("b", ct->blue);       /* table.b = ct->b */
      lua_setglobal(L, ct->name);    /* `name' = table */
    }
lua_newtable 函式建立一個空表格並將其推入堆疊;setfield 呼叫設定表格欄位;最後,lua_setglobal 彈出表格並將其設定為具有給定名稱的全球變數值。

使用這些先前的函式,下列迴圈將在應用程式的全球環境中註冊所有顏色

    int i = 0;
    while (colortable[i].name != NULL)
      setcolor(&colortable[i++]);
請記住,應用程式必須在執行使用者腳本之前執行此迴圈。

實作命名顏色還有另一種選擇。使用者可以字串表示顏色名稱,而不是使用全球變數,將其設定寫成 background = "BLUE"。因此,background 可以是表格或字串。使用此實作,應用程式不需要在執行使用者的腳本之前執行任何動作。不過,它需要更多工作才能取得顏色。當它取得變數 background 的值時,它必須測試值是否為字串類型,然後在顏色表格中查詢字串

    lua_getglobal(L, "background");
    if (lua_isstring(L, -1)) {
      const char *name = lua_tostring(L, -1);
      int i = 0;
      while (colortable[i].name != NULL &&
             strcmp(colorname, colortable[i].name) != 0)
        i++;
      if (colortable[i].name == NULL)  /* string not found? */
        error(L, "invalid color name (%s)", colorname);
      else {  /* use colortable[i] */
        red = colortable[i].red;
        green = colortable[i].green;
        blue = colortable[i].blue;
      }
    } else if (lua_istable(L, -1)) {
      red = getfield("r");
      green = getfield("g");
      blue = getfield("b");
    } else
        error(L, "invalid value for `background'");

哪個選項最好?在 C 程式中,使用字串表示選項並非良好的做法,因為編譯器無法偵測拼寫錯誤。然而,在 Lua 中,全球變數不需要宣告,因此當使用者拼錯顏色名稱時,Lua 不會發出任何錯誤訊息。如果使用者寫成 WITE 而不是 WHITEbackground 變數會收到 nil (未初始化變數 WITE 的值),而這就是應用程式所知道的全部:backgroundnil。沒有其他關於錯誤的資訊。另一方面,使用字串時,background 的值會是拼錯的字串;因此,應用程式可以將該資訊新增至錯誤訊息。應用程式也可以不分大小寫地比較字串,因此使用者可以寫成 "white""WHITE",甚至是 "White"。此外,如果使用者腳本很小,而且有許多顏色,那麼只為了讓使用者選擇幾個顏色而註冊數百種顏色 (並建立數百個表格和全球變數) 可能很奇怪。使用字串可以避免這種開銷。