第一版是針對 Lua 5.0 編寫的。雖然在很大程度上仍然適用於後續版本,但有些地方有所不同。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買本書,您也可以協助 支援 Lua 專案。
![]() |
用 Lua 程式設計 | ![]() |
第四部分。C API 第 25 章。擴充您的應用程式 |
讓我們採用這種態度:現在,我們也想要設定一個背景顏色給視窗。我們假設最終的顏色規格由三個數字組成,每個數字都是 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
而不是 WHITE
,background
變數會收到 nil (未初始化變數 WITE
的值),而這就是應用程式所知道的全部:background
是 nil。沒有其他關於錯誤的資訊。另一方面,使用字串時,background
的值會是拼錯的字串;因此,應用程式可以將該資訊新增至錯誤訊息。應用程式也可以不分大小寫地比較字串,因此使用者可以寫成 "white"
、"WHITE"
,甚至是 "White"
。此外,如果使用者腳本很小,而且有許多顏色,那麼只為了讓使用者選擇幾個顏色而註冊數百種顏色 (並建立數百個表格和全球變數) 可能很奇怪。使用字串可以避免這種開銷。
版權所有 © 2003–2004 Roberto Ierusalimschy。保留所有權利。 | ![]() |