1. 程式人生 > >快速掌握Lua 5.3 —— 編譯,執行以及錯誤

快速掌握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和錯誤資訊。