lua的全局變量、局部變量、函數閉包和非局部變量(upvalue)

我們知道lua腳本語言的變量是弱類型的,即變量沒有類型,值纔有類型,同一名稱的變量具體類型要看所賦值的類型,如下

a=1    --整型
a=1.0  --浮點型
a="ab" --string型 
a={}  --table型
a=function() ... end --function

全局變量和局部變量,類似於shell腳本
全局變量:顧名思義,其生命有效期是全局的,整個lua文件中都可以使用,可以在任意地方定義(函數參數除外),但有個原則,使用時必須是先定義好的,否則就是nil,請看下面的代碼

print(i);
function test(j)
   i = 1;
end
test(); --如果不執行test(), i未定義,都是nil
print(i,j);

執行結果是

nil
1   nil

局部變量:只在某些特定的範圍內有效的變量,稱爲局部變量,用local修飾。最主要的局部變量是定義在函數內部的局部變量。請看下面的代碼片段

local j=2; --雖然是定義的local變量,但卻是在函數外部,
           --所以其作用域是它之後的整個文件,所以等同於全局變量
function test()
   local i = 1; --局部變量,只在test函數內部有效
   print("j="..j);
end
test();
print(i,j);

執行結果爲:

j=2
nil 2

函數閉包
函數閉包:閉包(closure),通過調用含有一個內部函數加上該外部函數持有的外部局部變量(upvalue)的外部函數(就是工廠)產生的一個實例函數
閉包組成:外部函數+外部函數創建的upvalue+內部函數(閉包函數)
這裏,test是外部函數, local i是upvalue, function() i=i+1.. 是內部函數

function test()
        local i=0   --局部變量
        return function() --尾調用,即尾部調用,將另一函數作爲function類型返回
            i = i+1
            return i
        end
    end
    c1=test()
    c2=test(); --c1,c2是建立在同一函數,同一局部變量的不同實例上的兩個不同的閉包
            --閉包中的upvalue各自獨立,調用一次test()就會產生一個新的閉包
    print(c1()) -->1
    print(c1()) -->2//重複調用時每一個調用都會記住上一次調用後的值,就是說i=1了已經被記住了
    print(c2())    -->1//閉包不同所以upvalue不同    
    print(c2()) -->2

函數嵌套,即函數內部定義了另一個函數,注意不是函數調用。因爲lua將函數也看成是一種基本數據類型,可以用來做賦值和返回。實際也是秉承了c語言指針概念的基礎上做的轉換,將函數的地址作爲基本數據類型(個人理解,不確定正確,待考證)。

非局部變量,即上面提到的upvalue,這個值改如何理解,如果你有C語言基礎,我們不妨先將它理解爲函數內部定義的“靜態(static)變量”,但upvalue它有自己的特點。

首先看upvalue的定義要求,一定是外部函數定義的的local局部變量,且在內部函數調用過。如下圖中 j n k都是upvalue, 而h由於在內部函數中未調用,所以不是upvalue。(全局變量的作用域是全局的,所以談論全局變量做upvalue無意義)

function newCounter() --外部函數
    local j = 0; local n = 0;
    local k = 0; local h = 0;
    print("f1:",j,k,n);     --cd1
    return function() --內部函數
        print("f2:",j,k,n); --cd2
        j = n;
        k = n;
        n = n+1;
        return j,k,n;
    end
end
counter = newCounter(); --cd3
print("f3:",counter()); --cd4
print("----------分割線----------");
print("f4:",counter()); --cd5

執行結果

f1: 0   0   0
f2: 0   0   0
f3: 0   0   1
----------分割線----------
f2: 0   0   1
f4: 1   1   2

說明:cd3(code3)將函數賦值給counter,產生一個新實例,即生成一個閉包。從結果看cd4和cd5兩次調用counter, 但外部函數的cd1只執行了一次,所以可以看出,閉包函數的外部函數部分只在第一次調用時執行,後面再次時調用直接跳到內部函數執行,所以upvalue的j k n的值只在第一次由外部函數初始化給定,後續的值仍然以某種形式“存活”下來,因此這個特性有點類似於c語言的靜態變量。

如果上面的嵌套函數再次賦值給counter1,則會生成一個新的閉包實例,它和之前的counter之間是相互獨立的關係,我們看下面的代碼

function newCounter()
    local j = 0;local n = 0;local k = 0; local h = 0;
    print("f1:",j,k,n);
    return function()
        print("f2:",j,k,n);
        k = n;
        j = n;
        n = n+1;
        return j,k,n;
    end
end

counter = newCounter();
print("f3:",counter());
print("----------分割線----------");
print("f4:",counter());

print("**********分割線**********");

counter1 = newCounter();
print("f5:",counter1());
print("----------分割線----------");
print("f6:",counter1());

執行結果:

f1: 0   0   0
f2: 0   0   0
f3: 0   0   1
----------分割線----------
f2: 0   0   1
f4: 1   1   2
**********分割線**********
f1: 0   0   0
f2: 0   0   0
f5: 0   0   1
----------分割線----------
f2: 0   0   1
f6: 1   1   2

說明: * * * * * * *分割線 * * * * * * *,上面是counter的執行結果,下面是counter1的,兩次執行結果一樣,證明counter和counter1之間是獨立的。我們可以藉助c++的概念來理解,將閉包函數看做是創建類(class)的實例,counter和counter1分別是相同類(class)的兩個實例,之間沒有關聯關係,而upvalue則是對應的實例成員函數內部的靜態變量。

upvalue非局部變量可以使用debug.getupvalue和debug.setupvalue來獲取和設置,具體函數說明如下:

  • getupvalue (f, up):此函數返回函數 f 的第 up 個上值的名字和值。 如果該函數沒有那個上值,返回 nil 。 以 ‘(’ (開括號)打頭的變量名錶示沒有名字的變量 (去除了調試信息的代碼塊)
  • setupvalue (f, up, value):這個函數將 value 設爲函數 f 的第 up 個上值。 如果函數沒有那個上值,返回 nil 否則,返回該上值的名字

這裏補充一點up的排序規律,我們看下面的代碼

function newCounter()
    local j = 0;local n = 0;local k = 0; local h = 0;
    local l=0;
    print("f1:",j,k,n);
    return function()
        print("f2:",j,k,n);
        l = n;
        h = n;
        k = n;
        j = n;
        n = n+1;
        return j,k,n;
    end
end
counter = newCounter();
local i = 1;
print("-----------------------");
repeat
   name, val = debug.getupvalue(counter, i);
   if name then
       print("index", i, name, "=", val);
       if(name == "n") then
            debug.setupvalue(counter, 2, 10);
        end
        i = i + 1;
    end

until not name

print("-----------------------");
print("f3:",counter());

執行結果如下:

f1: 0   0   0
-----------------------
index   1   j   =   0
index   2   k   =   0
index   3   n   =   0
index   4   l   =   0
index   5   h   =   0
-----------------------
f2: 0   10  0
f3: 0   0   1

從結果上看upvalue的順序和內部函數return的順序有關,return 前的排在前面(j k n),沒有return的排在後面,且按照調用順序排列(l h)。

補充一下閉包定義的兩種寫法:

--寫法1
function f1(n)
    --local i=0;
   return function()
       print(n);
       return n;
    end
end


g1=f1(200);
print(g1()); --如果寫成g1(300),帶入的參數會被忽略

--寫法2
function f3(n)
    local i=0;
   function f4()
       print(n);
       return n;
    end
    return f4;
end

g2=f3(200);
print(g2());

好了,以上這些就是我個人的理解,有不對的請指正,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章