快速掌握Lua 5.3 —— 編譯,執行以及錯誤
Q:load()
,loadfile()
與dofile()
?
A:
1、
-- "a.lua"檔案中的內容。--
print("Hello Lua!")
------------------------
-- load()編譯指定字串中的程式碼,並將程式碼以一個函式的形式返回。
f = load("print(\"Hello Lua!\")")
f() --> Hello Lua!
-- loadfile()編譯指定檔案中的程式碼,並將程式碼以一個函式的形式返回。
f = loadfile("a.lua")
f() --> Hello Lua!
-- dofile()編譯指定檔案中的程式碼並執行。
dofile("a.lua") --> Hello Lua!
實際上dofile()
只是個輔助函式,它在內部呼叫了loadfile()
,
function dofile (filename)
local f = assert(loadfile(filename))
return f()
end
2、當指定的程式碼有語法錯誤時,load()
和loadfile()
都不會報錯,而只是返回錯誤碼以及錯誤描述,而dofile()
會報錯。所以load()
和loadfile()
使用更靈活,我們可以自己處理錯誤,而dofile()
使用更方便,一次呼叫完成所有任務,錯誤處理也可以託管給Lua。
Q:當load()
,loadfile()
以及dofile()
載入一個函式時,與普通的函式建立的區別?
A:load()
,loadfile()
以及dofile()
不關心”lexical scoping”,他們總是將自己放在全域性環境下。
-- "a.lua"檔案中的內容。--
i = i + 1
------------------------
i = 0 -- global i.
function print_global_i()
io.write(string.format("Global i is %d\t", i))
end
do
local i = 0
f = function () i = i + 1 end
f_load = load("i = i + 1")
f_loadfile = loadfile("a.lua")
f(); print_global_i(); print(string.format("Local i is %d.", i))
f(); print_global_i(); print(string.format("Local i is %d.", i))
f_load(); print_global_i(); print(string.format("Local i is %d.", i))
f_load(); print_global_i(); print(string.format("Local i is %d.", i))
f_loadfile(); print_global_i(); print(string.format("Local i is %d.", i))
f_loadfile(); print_global_i(); print(string.format("Local i is %d.", i))
dofile("a.lua"); print_global_i(); print(string.format("Local i is %d.", i))
dofile("a.lua"); print_global_i(); print(string.format("Local i is %d.", i))
end
Global i is 0 Local i is 1.
Global i is 0 Local i is 2.
Global i is 1 Local i is 2.
Global i is 2 Local i is 2.
Global i is 3 Local i is 2.
Global i is 4 Local i is 2.
Global i is 5 Local i is 2.
Global i is 6 Local i is 2.
Q:require()是如何工作的?
A:require()
與dofile()
的功能類似,他們之間的區別在於,
1、require()
會在指定的路徑(類似於”linux”的”PATH”)中搜索檔案。
2、require()
會自動識別檔案是否被載入過,以防止重複載入。
所以,require()更適合載入庫檔案(或者說叫“模組”,”modname”)。
require()
查詢檔案所使用的路徑類似於如下形式,
?;?.lua;c:\windows\?;/usr/local/lua/?/?.lua
其中;
分割每個路徑,?
代表在查詢過程中將被替換為檔名的部分。路徑中也可以不加問號,比如:
?;?.lua;/usr/local/default.lua
當require()
找不到要找的檔案時,就會載入這個指定的檔案(default.lua)。
require "lili" -- 如此呼叫,將嘗試開啟如下檔案(或者說叫“模組”,"modname")。
lili
lili.lua
c:\windows\lili
/usr/local/lua/lili/lili.lua
require()
使用package.loaded
表儲存已載入過的檔案,保證同一個檔案不會被重複載入,
-- "a.lua"檔案中的內容。--
a = 5
------------------------
require "a" -- 載入"a.lua"。
for i, v in pairs(package.loaded) do
print(i, v)
end
-- result
bit32 table: 0x1338800
debug table: 0x1335d90
io table: 0x13353a0
os table: 0x1335040
a true <-- "a.lua"已經被載入了
_G table: 0x13329f0
coroutine table: 0x1332770
package table: 0x1334470
table table: 0x13350d0
utf8 table: 0x1337b70
math table: 0x1334e80
string table: 0x1332df0
Q:如何觸發異常?
A:使用error()
,第一個引數指定錯誤資訊。
print "enter a number:"
n = io.read("*number") -- 規定輸入的是一個數字。
if not n then error("invalid input") end
Lua提供一種更優雅的方式,assert()
,
print "enter a number:"
n = assert(io.read("*number"), "invalid input") -- 如果io.read()的結果為假,則報錯,錯誤資訊為指定的資訊。
error()
第二個引數指定錯誤資訊中報告的出錯位置。1
代表出錯位置指向error()
被呼叫的位置,2
代表出錯位置指向呼叫error()
的函式被呼叫的位置。
function foo (str)
if type(str) ~= "string" then -- 你的函式中有個檢查,規定輸入引數一定是字串。
error("string expected")
end
end
-- call
--[[ 如果有這樣的呼叫,
Lua會將出錯的地方指向你的判斷部分(即foo()中呼叫error()的部分),
因為error()第二個引數預設值為1。]]
foo({x = 1})
這看起來有些不妥,因為並不是函式中對型別判斷有問題,實際上是呼叫的地方出了問題。那麼我們可以將error()
的第二個引數指定為2
,
error("string expected", 2) -- 此時錯誤資訊將指向foo()的部分。
Q:如何控制報錯的方式?
A:如果你不想讓程式受到異常的影響而中斷,即使是一些Lua當錯誤發生時預設會丟擲異常的情況(比如字串與數字比較大小),那麼你可以使用pcall()
。pcall()
在安全模式下呼叫你指定的函式,這意味著被調函式中所有的異常都會被pcall()
捕獲。pcall()
接收被調函式以及被調函式的引數,如果被調函式在執行過程中沒有觸發異常,則它返回true
以及被調函式所有的返回值,否則它返回false
以及錯誤資訊。
function foo(var1, var2)
return (var1 > var2)
and var1 .. " is larger than " .. var2
or var1 .. " is smaller than " .. var2
end
r, s = pcall(foo, 1, 0)
print(r, s) --> true 1 larger than 0
r, s = pcall(foo, -1, 0)
print(r, s) --> true -1 smaller than 0
r, s = pcall(foo, "1", 0)
print(r, s) --> false attempt to compare number with string
同時pcall()
也接受匿名函式,還是上面的例子,改成如下亦可,
r, s = pcall(function (var1, var2)
return (var1 > var2)
and var1 .. " is larger than " .. var2
or var1 .. " is smaller than " .. var2
end, "1", 0)
錯誤資訊不僅可以是字串,其他任何lua的值均可,
local status, err = pcall(function () error({code=121}) end)
print(err.code) --> 121
附加:
1、Lua雖然是解釋型語言(就像”shell”一樣),但是它也會將原始碼編譯。這並不奇怪,因為許多解釋型語言都是這麼做的。解釋型語言與編譯型語言的差異在於,解釋型語言的編譯過程是在執行過程中完成的,並且在執行過程中無處不在。
2、注意,對於使用loadfile()
載入一個儲存函式的檔案時,loadfile()
只是將建立函式的程式碼進行了編譯,並沒有執行建立函式的過程(即沒有宣告函式),
-- "a.lua"檔案中的內容。--
function foo(var)
print(var)
end
------------------------
f = loadfile("a.lua")
foo(5) --> attempt to call a nil value (global 'foo')
f() -- 執行建立函式的過程,即宣告函式。 -- 其實這裡就是在執行"a.lua"中的程式碼。
foo(5) --> 5
3、如果我們想多次使用同一個檔案,那麼呼叫一次loadfile()
然後多次使用他的結果,這樣比多次呼叫dofile()
要好(這樣會多次讀取檔案,因為其內部多次loadfile()
)。
4、”Q & A”中提到的load()
,loadfile()
以及dofile()
的使用方法均是使用他們載入語句(載入的結果不能賦值給其他變數或函式),而如果你想載入表示式(載入的結果可以賦值給其他變數或函式),需要在程式碼前面加上一個return
,
print "enter function to be plotted (with variable 'x'):"
local l = io.read()
local f = assert(load("return " .. l)) -- 返回表示式。
for i = 1, 10 do
x = i -- global 'x' (to be visible from the chunk)
print(string.rep("*", f())) -- "f()"將被替換為使用者輸入的程式碼。
end
-- result
> 2*x
**
****
******
********
**********
************
**************
****************
******************
********************
5、當一個函數出現異常時,它有兩種方式可以選擇:
(1) 返回錯誤碼(經典的情況是返回nil)。
(2) 報錯。
沒有固定的規則說明當錯誤發生時該選擇哪一種方式,但是有一條指導方針:如果一個錯誤很容易被避免(而且是沒有合理的理由證明它應該出現的),那麼當這種錯誤發生時就報錯;否則,將會返回錯誤碼。
舉個例子,當math.sin()
的引數是一個”table”時我們該怎麼做?假設我們返回一個錯誤碼,那麼如果我們想判斷是否出錯也許應該這麼寫,
local res = math.sin(x)
if not res then -- error
...
或者更簡單的,我們可以在呼叫math.sin()
之前檢查引數,
if not tonumber(x) then -- error: x is not a number
...
但是通常我們既不檢查引數,也不檢查函式返回結果。如果引數不是數字,說明我們的程式在哪裡出了問題。如果是那樣的話,觸發異常是最簡單,最直接的方法,這類錯誤屬於很容易被避免的,而且是沒有合理的理由證明它應該出現的。
接下來再考慮io.open()
開啟一個不存在檔案的情況。在呼叫io.open()
之前沒有簡單的方法可以檢測出檔案是否存在,並且在很多系統上,判斷一個檔案是否存在的方法就是去嘗試開啟它。所以當io.open()
無法開啟檔案時,它返回一個nil
加上一段錯誤描述,這樣你就可以合理的處理這些錯誤。
6、當錯誤發生的時候,我們常常希望瞭解更詳細的資訊(比如函式呼叫棧資訊),而不僅是錯誤發生的位置。但pcall()
返回錯誤資訊時,已經釋放了儲存錯誤發生情況的棧資訊。所以如果我們想獲取,需要在pcall()
被呼叫之前獲取。然而,Lua提供了xpcall()
,它比pcall()
多接收一個錯誤處理函式。當錯誤發生時,在函式呼叫棧資訊被釋放之前,Lua會呼叫指定的錯誤處理函式,這樣就可以在該錯誤處理函式中使用debug
庫獲得更多的資訊。debug
庫中兩個常用的函式,
debug.debug() -- 進入互動模式(類似於Lua的互動模式)。輸入命令檢視資訊。
-- lua互動模式報錯時也是通過此函式列印的函式呼叫棧資訊。
debug.traceback() -- 列印函式呼叫棧資訊。
7、Lua在package.loadlib()
中提供了所有的動態連線的功能。這個函式有兩個引數:庫的絕對路徑和初始化函式。所以典型的呼叫的例子如下:
local path = "/usr/local/lua/lib/libluasocket.so"
local f = package.loadlib(path, "luaopen_socket")
package.loadlib()
載入指定的庫並且連線到Lua,然而它並不開啟庫(也就是說沒有呼叫初始化函式)。他返回初始化函式作為Lua的一個函式,這樣我們就可以直接在Lua中呼叫他。如果載入動態庫或者查詢初始化函式時出錯,package.loadlib()
將返回nil
和錯誤資訊。