範圍教學 |
|
local
關鍵字來控制變數存在的位置。
此頁面的範例會撰寫成腳本檔案的形式,而不是互動式直譯器階段,因為在其中很難處理區域變數。稍後說明。
若要建立區域變數,請在指定項目的前面加上 local
關鍵字。
local a = 5 print(a)
變更變數時不必再使用 local
關鍵字。
local a = 5 a = 6 -- changes the local a, doesn't create a global
區域變數僅存在於它們所建立的區塊。在區塊外,它們不再存在。
local a = 5 print(a) --> 5 do local a = 6 -- create a new local inside the do block instead of changing the existing a print(a) --> 6 end print(a) --> 5
變數可見的位置稱為變數的「範圍」。
現在讓我們使用函式來展示它的實用性。
function bar() print(x) --> nil local x = 6 print(x) --> 6 end function foo() local x = 5 print(x) --> 5 bar() print(x) --> 5 end foo()
如您所見,每個變數都可從宣告的位置看到區塊的結尾。即使 bar
的 x 與 foo
的 x 同時存在,它們並未寫在同一個區塊,因此各自獨立。這就是所謂的 詞彙範圍。
local function
語法精簡
local function f() end -- is equivalent to local f f = function() end -- not local f = function() end
最後兩個範例間的差異很重要:區域變數在給予初始化值的 =
的右側仍不存在。因此,如果該函式的內容使用 f
取得其本身的參考,它會正確取得第一個範例中的區域變數和第二個範例中的區域變數,但第三個範例會取得全域性 f(如不是由部分其他程式碼設定的完全不相干值,則為 nil)。
函式可以使用在它們之外所建立的區域變數。這些稱為「向上值」。使用向上值的函式稱為「封閉」。
local x = 5 local function f() -- we use the "local function" syntax here, but that's just for good practice, the example will work without it print(x) end f() --> 5 x = 6 f() --> 6
函式會看到這個變更,即使是在函式外變更也是如此。表示函式中變數不是複本,它與外部範圍會共用。
而且,即使外部範圍已經傳遞,函式仍會保有這個變數。如果在範圍中建立了兩個函式,在外部範圍消失後,它們仍會共用變數。
local function f() local v = 0 local function get() return v end local function set(new_v) v = new_v end return {get=get, set=set} end local t, u = f(), f() print(t.get()) --> 0 print(u.get()) --> 0 t.set(5) u.set(6) print(t.get()) --> 5 print(u.get()) --> 6
由於 f
的兩個呼叫傳回的兩個值是獨立的,我們可以看到每次呼叫函式時,它都會建立一個具有新變數的新範圍。
同樣的,迴圈在每次迭代時會建立一個新範圍。
local t = {} for i = 1, 10 do t[i] = function() print(i) end end t[1]() --> 1 t[8]() --> 8
因為它在新的範圍內執行每一行
> local a=5; print(a) 5 > print(a) -- a is out of scope now, so global a is used nil
你可以使用 do-end 塊包裝程式碼,但它不會在完成整個塊的寫入之前互動
> do >> local a = 5 >> print(a) -- works on a new line >> end 5
你可能來自因為在預設值使用ローカル的語言,但可能會在想『所有這些額外複雜性有什麼意義?為何不在預設值使用ローカル變數?』
x = 3 -- more code, you might have even forgotten about variable x by now... function () -- ... x = 5 -- does this create a new local x, or does it change the outer one? -- ... end -- some more code...
變更外部變數的問題在於你可能會想要建立新的變數,但卻變更現有的變數,甚至你可能不知道的變數,這會導致錯誤。
建立新的變數的問題是,你實際上可能想要變更外部變數?
使用 local 關鍵字,所有都是明確的:如果不是 local
,你會變更現有的變數,如果是,你會建立新的變數。
有關其更多討論,請參閱 LocalByDefault。
一般規則是始終使用 local 變數,除非程式碼的每個部分都需要使用變數(這是非常罕見的情況)。
由於很容易忘記 local
,而且 Lua 也不會警告你(而是靜默建立全域變數),這可能會是錯誤的根源。一個解決方案是使用像 strict.lua
的指令碼(如下所示),它使用後續教學中提到的元表來攔截全域變數建立並引發錯誤。你可以將指令碼放置在專案的檔案中,並執行 require("strict")
來使用它。
-- -- strict.lua -- checks uses of undeclared global variables -- All global variables must be 'declared' through a regular assignment -- (even assigning nil will do) in a main chunk before being used -- anywhere or assigned to inside a function. -- local mt = getmetatable(_G) if mt == nil then mt = {} setmetatable(_G, mt) end __STRICT = true mt.__declared = {} mt.__newindex = function (t, n, v) if __STRICT and not mt.__declared[n] then local w = debug.getinfo(2, "S").what if w ~= "main" and w ~= "C" then error("assign to undeclared variable '"..n.."'", 2) end mt.__declared[n] = true end rawset(t, n, v) end mt.__index = function (t, n) if not mt.__declared[n] and debug.getinfo(2, "S").what ~= "C" then error("variable '"..n.."' is not declared", 2) end return rawget(t, n) end function global(...) for _, v in ipairs{...} do mt.__declared[v] = true end end
更多有關強制使用 local 變數的資訊,請參閱 DetectingUndefinedVariables。