範圍教學

lua-users home
wiki

到目前為止,您只設定變數名稱的值,並且在腳本中的任何地方使用名稱就能取得值。這適用於小型的範例,但是現在您了解函式後,這可能是個大問題:如果不同的函式使用相同名稱儲存暫時值,會怎樣?它們會衝突並覆寫彼此,讓您的腳本成為難以除錯的混亂。解決方案是使用 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 變數,除非程式碼的每個部分都需要使用變數(這是非常罕見的情況)。

由於很容易忘記 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


最近變更 · 偏好設定
編輯 · 歷史記錄
最後編輯日期 2013 年 12 月 21 日上午 10:07 GMT (diff)