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ローカル変数であり、内部関数で呼び出されたに違いない.下図のjn 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

説明:cd 3(code 3)はcounterに関数を割り当て、新しいインスタンスを生成します.すなわち、閉パッケージを生成します.結果からcd 4とcd 5はcounterを2回呼び出すが、外部関数のcd 1は1回しか実行されないので、閉パケット関数の外部関数部分は1回目の呼び出し時にのみ実行され、その後の再度の呼び出しは直接内部関数実行にジャンプするため、upvalueのjknの値は1回目に外部関数によって初期化されて与えられるだけであることがわかる.後続の値は依然として何らかの形で「生存」しているため、この特性はc言語の静的変数に少し似ている.
上記のネスト関数がcounter 1に再割り当てされると、前の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の実行結果、以下はcounter 1で、2回の実行結果と同様に、counterとcounter 1の間が独立していることを証明します.c++の概念を用いて,閉パケット関数をクラス(class)を作成するインスタンスと見なし,counterとcounter 1はそれぞれ同じクラス(class)の2つのインスタンスであり,間には関連関係がなく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).
    クローズ定義の2つの書き方を補足します.
    --  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());
    

    はい、以上は私の個人的な理解で、間違っていることがあったら指摘してください.ありがとうございます.