停用的 Lua Faq |
|
你不需宣告變數,Lua 沒有變數宣告,也不需要。
不過,為何你非要宣告變數呢?
部分人希望宣告變數來防止輸入錯誤。例如,以下程式碼
color="red" print(colour)
將印出 nil
,因為第 2 行錯誤輸入了 color
,而並未定義 colour
。
為防止發生此類錯誤,同時找出未定義的變數,你可以使用 getglobal
標籤方法
function safe_getglobal(x) local v=rawgetglobal(x) if v then return v else error("undefined global variable "..x) end end settagmethod(tag(nil),"getglobal",safe_getglobal)
加入此程式碼後,任何嘗試使用未定義變數(值為 nil
)的行為都將產生錯誤訊息。
Lua 是一種動態型別語言。這表示變數沒有型別,只有值才有。由於 Lua 提供存取值型別的管道(透過 type
函式),你可以撰寫自己的動態型別函式。以下是其中一種可能的實作方式。
若要檢查簡單型別,只需使用 type
。檢查表格的案例較有趣,因為必須確認所有欄位都存在且填寫正確,才能符合預先定義的「範本」。因此,我們需要一種方式來描述表格的「型別」。如上所述,簡單型別由名稱描述,且名稱由 type
函式回傳。表格則由「型別範本」描述,這些範本是將每個欄位對應其所需型別的表格。以下是對使用者介面工具組好用的範例
TNumber="number" TPoint={x=TNumber, y=TNumber} TColor={red=TNumber, blue=TNumber, green=TNumber} TRectangle={topleft=TPoint, botright=TPoint} TWindow={title="string", bounds=TRectangle, color=TColor}
提供此類描述後,下列函式將檢查值是否具有給定的型別
function checkType(d, t) if type(t) == "string" then -- t is the name of a type return (type(d) == t) else -- t is a table, so d must also be a table if type(d) ~= "table" then return nil else -- d is also a table; check its fields local i,v = next(t,nil) while i do if not checkType(d[i],v) then return nil end i,v = next(t,i) end end end return 1 end
在大型專案中,以及在程式庫中,偶爾必須保護某些變數,使其無法重新定義。當載入多個程式庫時,而且你想要確認它們不會意外地重新定義彼此的函式時,這項功能特別好。您
你可以利用 setglobal
標籤方法保護 Lua 中的函式
function protect(x) error("cannot redefine "..x) end settagmethod(tag(protect),"setglobal",protect)
此程式碼應在載入所有程式庫後執行。
加入此程式碼後,任何嘗試重新定義函式的行為都將產生錯誤訊息。你仍可以重新定義函式,但你必須明確地這麼做
rawsetglobal("print",nil) function print (x) ... end
若要將此架構擴充套用至其他種類的值,你需要使用表格包裝值,同時使用 setglobal
和 getglobal
標籤方法
RO=newtag() -- tag for read-only values function ROvalue(value) -- make a read-only value local t={value=value} settag(t,RO) return t end function ROsetglobal(x) -- protects assignment error(x.." is read-only") end function ROgetglobal(x,value) -- get the actual value return value.value end settagmethod(RO,"getglobal",ROgetglobal) settagmethod(RO,"setglobal",ROsetglobal) tolerance=ROvalue(0.12) -- make some read-only variables color=ROvalue("red") myprint=ROvalue(print)
此架構僅影響使用 ROvalue
建立的變數,因為只有這些變數擁有 RO
標籤。
Lua 5.0 新增區塊註解語法
--Double-dashes begin a single-line comment --[[Double-dashes immediately followed by double left square bracket Begin a multi-line comment That continues until the __matching__ double right square bracket, so you can do [[this]] too. ]]
Lua 沒有區塊註解的特殊語法。一種解決方案是將註解封裝在字串中
comment = [[
this is my
multi-line comment
]]
以下為此解決方案的變體,此變體方便暫停區塊程式碼
comment = [[
disabled code
--]]
只需在指定項目的替碼中加上--
即可啟用。
--comment = [[ enabled code --]]
如果區塊過大,您可能需要避免在 garbage collector 移除前,讓其浮動在記憶體中。接著,您可以撰寫
local comment = [[ disabled code --]] ; comment = nil
Luiz Henrique de Figueiredo 也建議
do local C=[[ --]] end
"C 隨即超出作用範圍。剖析器可以利用這一點 (但並未利用)。此外還有:"
local C= 1 or [[ <syntaxly correct code to comment out> --]]
"程式碼產生器可以利用這一點 (但並未利用)。"
另一種解法是使用編輯器巨集,在所選行之前放置"-- ";或例如"--~ ",如此註解的區塊便會從周遭的正常註解中跳脫出來。
若要註解某個程式碼區塊,可以使用if nil then ... end
。(請參閱下一個問題。)
在 Lua 中,會透過參照處理表格。因此,如果x
的值是表格,那麼指定項目y=x
並不會複製表格:x
和y
包含同一個表格。(換句話說,x
和y
的值是同一表格的參照。)
如果您真正想要複製一個表格,可以使用內建函式next
,如下面的程式碼所示
function clone(t) -- return a copy of the table t local new = {} -- create a new table local i, v = next(t, nil) -- i is an index of t, v = t[i] while i do new[i] = v i, v = next(t, i) -- get next index end return new end
如果您想要深度複製,在new[i] = v
之前,請加上if type(v)=="table" then v=clone(v) end
。
在 Lua 中實作集合最好的方式是將其元素儲存在表格的鍵值中。當表格中對應的值不為nil
時,表示元素存在於集合中。
下面是一些集合的程式碼片段
s={} -- create an empty set s[x]=1 -- insert element x into set s s[x]=nil -- remove element x from set s x_is_present=s[x] -- does s contain x?
多重集合類似於集合,除了元素可能出現多次。多重集合可以實作的方式類似於集合,不過是使用與元素相關聯的值作為其計數器。下面是一個多重集合的程式碼片段
-- insert element x into bag s if s[x] then s[x] = s[x]+1 else s[x] = 1 end -- remove element x from set s if s[x] then s[x] = s[x]-1 if s[x] == 0 then s[x] = nil end end
使用 Lua 5.0,請使用string.char(n)
,甚至可以使用string.format("%c", n)
。在 Lua 5.0 之前,請使用strchar(n)
或 format("%c",n)
。
[需要更新!]
read
的樣式比對功能強大,但並非所有人都會撰寫樣式。以下是一些有用的樣式
Word (sequence of letters and digits) {{"%w%w*"}} Word (sequence of non-white space characters) {{"%S%S*"}} Integer {{"[+-]?%d%d*"}} Real {{"[+-]?%d%d*[.]%d%d*"}} Double quoted string {{'"[^"]*"'}} Single quoted string {{"'[^']*'"}}
如果您要在讀取項目之前跳過空白,請在樣式之前放置"{%s*}"
。例如,"{%s*}%S%S*"
會跳過空白,然後讀取下一個字詞。
第 3.2 版讓這一切變得更簡單:您可以使用"*w"
來讀取一個字詞,以及"*n"
來讀取一個數字。請參閱手冊,以獲得預先定義樣式的完整清單。
使用任何語言撰寫自我複製程式很有趣,但並不總是容易,因為您必須小心引號。由於 Lua 提供備用引號,所以執行起來很簡單
y = [[ print("y = [[" .. y .. "]]\ndostring(y)") ]]
dostring(y)
版本注意事項:以上是 Lua 4.0。
若要取得其他語言的自我複製程式,請參閱 Quine 網頁 [1]。也可以閱讀關於自我複製程式運作方式的相關說明 [2]。
[需要更新!]
執行下列程式碼
function save() local g={} foreachvar(function (n,v) %g[n]=v end) return g end function restore(g) foreach(g,setglobal) end
自 3.1 版本開始,使用 C,搭配 lua_open
、lua_close
和 lua_setstate
,就能執行這些相同的操作,甚至更多。
[需要更新!]
您必須遵循一個簡單的協定:使用 lua_getparam
取得 Lua 的任何參數,確定參數類型是否正確並具備適當的 lua_is...
函式,搭配適當的 lua_get...
函式轉換參數,對轉換後的參數進行相關處理,並透過適當的 lua_push...
函式將結果推回 Lua。
來看一個實際範例,並將 getenv
函式匯出至 Lua。更多範例請參閱實作標準函式庫的程式碼。
C 中的 getenv
函式會取得一個字串,並回傳另一個字串。其原型是
char* getenv(char*);
因此,適當的呼叫函式為 lua_isstring
、lua_getstring
和 lua_pushstring
void wrap_getenv(void) { lua_Object o=lua_getparam(1); if (lua_isstring(o)) lua_pushstring(getenv(lua_getstring(o))); else lua_error("string expected in argument #1 to getenv"); }
自 3.0 版本開始,Lua 納入了簡化封裝寫作的輔助函式。有了這些函式,我們可以將 wrap_getenv
寫成
void wrap_getenv(void) { lua_pushstring(getenv(luaL_check_string(1))); }
完成封裝後,您必須呼叫 lua_register("getenv",wrap_getenv)
以讓 Lua 能使用封裝。這是標準函式庫所執行的內容
請為函式庫中的每個函式撰寫一份封裝,如上所述。
已提供數個可自動化此程序的解決方案。請參閱 LuaAddons 中的「Code wrappers」
VersionNotice:這是 Pre-Lua5。
除了無限遞迴等簡單情況外,如果您在迴圈中從 C 呼叫 Lua,就會收到這則訊息,因為參數和回傳值會累積在 Lua 的堆疊中。例如,這段程式碼將會產生「堆疊大小溢位」
for (i=0;;i++) { lua_pushnumber(i); lua_call("print"); }
解決方案是將與 Lua 對應的程式碼用於呼叫 lua_beginblock
和 lua_endblock
for (i=0;;i++) { lua_beginblock(); lua_pushnumber(i); lua_call("print"); lua_endblock(); }
引用自參考手冊:強烈建議使用明確的嵌套區塊。