第一版是為 Lua 5.0 編寫的。儘管在很大程度上仍然適用於後續版本,但仍有一些區別。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買本書,您還可以幫助 支持 Lua 項目。
![]() |
用 Lua 編程 | ![]() |
第一部分。語言 第 8 章。編譯、執行和錯誤 |
儘管我們將 Lua 稱為直譯語言,但 Lua 總會在執行源代碼之前將其預編譯為中間形式。(這不是什麼大問題:大多數直譯語言都這樣做。)在 Lua 這樣的直譯語言中,編譯階段的存在聽起來似乎不合適。然而,直譯語言的顯著特徵並非它們沒有編譯,而是任何編譯器都是語言運行時的一部分,因此,執行動態生成的代碼是可能的(而且容易)。我們可以說,像 dofile
這樣的函數的存在允許 Lua 被稱為直譯語言。
之前,我們將 dofile
作為一種運行 Lua 代碼塊的原始操作。dofile
函數實際上是一個輔助函數;loadfile
執行艱苦的工作。與 dofile
類似,loadfile
也從文件中加載一個 Lua 塊,但它不運行該塊。相反,它只編譯該塊並將編譯後的塊作為函數返回。此外,與 dofile
不同,loadfile
不會引發錯誤,而是返回錯誤代碼,以便我們可以處理錯誤。我們可以定義 dofile
如下
function dofile (filename) local f = assert(loadfile(filename)) return f() end請注意,如果
loadfile
失敗,則使用 assert
引發錯誤。
對於簡單的任務,dofile
很方便,因為它在一個呼叫中完成整個工作。然而,loadfile
更靈活。如果發生錯誤,loadfile
會傳回 nil 和錯誤訊息,這讓我們可以自訂方式來處理錯誤。此外,如果我們需要執行一個檔案好幾次,我們可以呼叫 loadfile
一次,然後呼叫其結果好幾次。這比呼叫 dofile
好幾次便宜許多,因為程式只會編譯檔案一次。
loadstring
函式類似於 loadfile
,除了它從字串讀取區塊,而不是從檔案讀取。例如,在程式碼之後
f = loadstring("i = i + 1")
f
會是一個函式,當呼叫時,會執行 i = i + 1
i = 0 f(); print(i) --> 1 f(); print(i) --> 2
loadstring
函式很強大;必須小心使用。它也是一個昂貴的函式(與其替代方案相比),而且可能會產生難以理解的程式碼。在你使用它之前,請確定沒有更簡單的方法來解決目前的問題。
Lua 將任何獨立區塊視為匿名函式的本體。例如,對於區塊 "a = 1"
,loadstring
會傳回等同於
function () a = 1 end就像任何其他函式一樣,區塊可以宣告局部變數和傳回值
f = loadstring("local a = 10; return a + 20") print(f()) --> 30
loadstring
和 loadfile
都永遠不會引發錯誤。如果發生任何類型的錯誤,這兩個函式都會傳回 nil 和錯誤訊息
print(loadstring("i i")) --> nil [string "i i"]:1: `=' expected near `i'此外,這兩個函式永遠不會有任何類型的副作用。它們只會將區塊編譯成內部表示形式,並傳回結果,作為匿名函式。一個常見的錯誤是假設
loadfile
(或 loadstring
)定義函式。在 Lua 中,函式定義是指定;因此,它們是在執行時間建立,而不是在編譯時間建立。例如,假設我們有一個檔案 foo.lua
如下
-- file `foo.lua' function foo (x) print(x) end然後我們執行指令
f = loadfile("foo.lua")在此指令之後,
foo
已編譯,但尚未定義。若要定義它,你必須執行區塊
f() -- defines `foo' foo("ok") --> ok
如果你想執行一個快速且隨意的 dostring
(即載入並執行一個區塊),你可以直接呼叫 loadstring
的結果
loadstring(s)()然而,如果有任何語法錯誤,
loadstring
會傳回 nil,而最後的錯誤訊息會是 "嘗試呼叫 nil 值"
。若要取得更清楚的錯誤訊息,請使用 assert
assert(loadstring(s))()
通常,對文字字串使用 loadstring
沒有意義。例如,程式碼
f = loadstring("i = i + 1")大致等同於
f = function () i = i + 1 end但第二段程式碼快很多,因為它只在區塊編譯時編譯一次。在第一段程式碼中,每次呼叫
loadstring
都會進行一次新的編譯。然而,這兩段程式碼並不完全等價,因為 loadstring
沒有使用詞彙範圍編譯。為了看出差異,讓我們稍微變更一下先前的範例
local i = 0 f = loadstring("i = i + 1") g = function () i = i + 1 end
g
函數處理本地的 i
,正如預期,但 f
處理全域的 i
,因為 loadstring
總是在全域環境中編譯字串。
loadstring
最典型的用途是執行外部程式碼,也就是來自程式外部的程式碼片段。例如,您可能想要繪製使用者定義的函數;使用者輸入函數程式碼,然後您使用 loadstring
來評估它。請注意,loadstring
預期一個區塊,也就是陳述式。如果您想要評估一個表達式,您必須在前面加上 return,這樣您才能取得一個傳回給定表達式值的陳述式。請看範例
print "enter your expression:" local l = io.read() local func = assert(loadstring("return " .. l)) print("the value of your expression is " .. func())
loadstring
傳回的函數是一個常規函數,因此您可以呼叫它多次
print "enter function to be plotted (with variable `x'):" local l = io.read() local f = assert(loadstring("return " .. l)) for i=1,20 do x = i -- global `x' (to be visible from the chunk) print(string.rep("*", f())) end
在需要執行外部程式碼的生產品質程式中,您應該處理 loadstring
報告的任何錯誤。此外,如果無法信任程式碼,您可能想要在受保護的環境中執行新的區塊,以避免執行程式碼時產生不愉快的副作用。
版權所有 © 2003–2004 Roberto Ierusalimschy。保留所有權利。 | ![]() |