Lua 言語プログラミング学習ロード 02----第 13 章 メタテーブルとメタメソッド

序文

    Lua では、2 つのテーブルを直接追加したり、関数を比較したり、関数を呼び出したりすることはできません。

したがって、Lua は、事前定義されていない操作に直面しても自己実装された操作を実行できるように、値の動作を変更できます。たとえば、2 つのテーブル a+b を追加します。a と b を両方のテーブルとします。Lua が a+b を計算しようとするとき、2 つのうちのいずれかにメタテーブルがあるかどうかを確認し、メタテーブルに __add の関数フィールドがあるかどうかを確認します。見つかった場合は、それがメタテーブルに含まれます。対応するフィールドの値を呼び出します。この値は「メタメソッド」、つまり関数です。

    Lua のすべての値にはメタテーブルがあります。table と userdata は独自の個別のメタテーブルを持つことができますが、他の型はその型が属する単一のメタテーブルを共有します。ただし、Lua はテーブルのメタテーブルの変更のみをサポートしており、他のメタテーブルは基礎となる C コードにアクセスして完了する必要があります。

テーブルのメタテーブルを取得します。

t = {}
print(getmetatable(t))
--设置b为a的元表
setmerarable(a,b)

13.1 算術クラスのメタメソッド

メタメソッドを使用してセットの結合と交差を実装します。


Set = {}
local mt = {}

function Set.new(l)
    local set = {}
    setmetatable(set, mt)
    for _,v in pairs(l) do set[v] = true end
    return set
end

function Set.union(a, b)

    local res = Set.new({})
    for k in pairs(a) do res[k] = true end
    for k in pairs(b) do res[k] = true end
    return res
end

function Set.intersection(a, b)
    local res = Set.new({})
    for k in pairs(a) do
        res[k] = b[k]
    end
    return res
end

function Set.tostring(set)
    local l = {}
    for e in pairs(set) do
        l[#l+1] = e
    end
    return "{"..table.concat(l,", ").."}"
end

function Set.print(s)
    print(Set.tostring(s))
end

mt.__add=Set.union
s1=Set.new({10,20,30,50})
s2=Set.new({1,30})
s3 = s1 + s2

mt.__mul=Set.intersection

Set.print(s3)
Set.print(s1*s2)

 コードには __add と __mul に加えて、__sub (減算)、__div (除算)、__unm (反対の数)、__mod (剰余)、__pow (べき乗)、__concat などもあります。

Lua 検索メタテーブル ルール: 最初の値にメタテーブルがあるかどうかを検索し、メタテーブルに __add フィールドがあると、これはメタメソッドとして計算され、2 番目の値とは関係がありません。逆に、2 番目の関数にメタテーブルがあり、__add フィールドがある場合、これは計算関数です。なし。エラーが発生します。

13.2 リレーションシップクラスのメタメソッド

__eq (等しい)

__lt (未満)

__le (以下)

13.3 ライブラリ定義のメソッド

ストリング

メタテーブルを設定する

メタ可能にする

13.4 テーブルアクセスのメタメソッド

13.4.1 __index メタメソッド

実際、テーブルに存在しないフィールドにアクセスする場合、Lua の検索パスは __index と呼ばれるメタメソッドを見つけることになります。そうでない場合、アクセスの結果は nil になります。それ以外の場合、最終結果は __index から提供されます。

これは継承の概念に似ており、サブクラスに属性がない場合、親クラスに属性を探しに行きます。Lua では、__index メタメソッドを使用して継承を実装するのが一般的です。__index は必ずしも関数である必要はなく、テーブルである場合もあります。関数の場合、Lua はパラメータとして存在しないテーブルとキーを使用してこの関数を呼び出します。テーブルの場合も同様にこのテーブルを再訪します。

13.4.2 __newindex メタメソッド

テーブルに存在しないフィールドに値を割り当てる場合、インタープリターは __newindex メタメソッドを探します。__newindex がテーブルの場合は、このテーブルの値を割り当てます。

ただし、 rawset(table,k,v) は、テーブルのすべてのメタメソッドをバイパスし、テーブルを強制的に割り当てることができます。

13.4.3 デフォルト値を含むテーブル

__index メタテーブルを通じてデフォルト値のコードを実装します。

function setDefault(t, d)
    local mt = {__index = function() return d  end}
    setmetatable(t, mt)
end

tap = {x=10,y=20}
print(tap.x, tap.z)
setDefault(tap, 0)
print(tap.x, tap.z)

setDefault 関数は、デフォルト値を必要とするすべてのテーブルに対して新しいメタテーブルを作成しますが、複数のテーブルがある場合、これは非常に高価になる可能性があります。複数のテーブルが同じデフォルトのメタテーブルを使用できれば素晴らしいのですが、実際には非常に簡単です。上記のコードの mt をグローバル変数として設定すれば問題ありません。コードは次のとおりです。

local mt = {__index = function(t) return t.___  end}
function setDefault(t, d)
    t.___ = d
    setmetatable(t, mt)
end

tap = {x=10,y=20}
print(tap.x, tap.z)
setDefault(tap, 0)
print(tap.x, tap.z)

13.4.4 テーブルアクセスの追跡

テーブルの変更を監視できる機能を実装し、

t = {}
local _t = t
t = {}
local mt = {
    __index = function(t, k)
        print("access to element"..tostring(k))
        return _t[k]
    end,
    __newindex = function(t, k , v)
        print("update of ellement"..tostring(k))
        _t[k] = v
    end
}

setmetatable(t, mt)

t[2] = "hello"
print(t[2])

欠点: 1. 元のテーブル、つまり _t をトラバースすることは不可能です。2. 複数のモニターに対して複数のメタテーブルがあり、オーバーヘッドが無駄になります

向上:

local index = {}
t = {}
local mt = {
    __index = function(t, k)
        print("access to element"..tostring(k))
        return t[index][k]
    end,
    __newindex = function(t, k , v)
        print("update of ellement"..tostring(k))
        t[index][k] = v
    end
}

function track(t)
    local answer = {}
    answer[index] = t
    setmetatable(answer, mt)
    return answer
end

tmp = {}
tmp = track(tmp)

tmp[2] = "hello"
print(tmp[2])

操作結果:

13.4.5 読み取り専用テーブル

更新操作 (__newindex) を無効にするだけです

function readonly(t)
    local answer = {}
    mt = {
        __index = t,
        __newindex = function(t, k ,v)
            error("update error")
        end
    }
    setmetatable(answer, mt)
    return answer

end

days = {1,2,3}

days = readonly(days)

print(days[1])
days[1]=2

さて、第14章の知識ポイントについてお話します。

setfenv(f, table): 関数の環境を設定します。

(1) 最初のパラメータが関数の場合、関数の環境設定を意味します (2) 最初のパラメータが数値の場合、1 は現在の関数を表し、2 は独自の関数 (つまり、前の関数の呼び出し) を表します)、3 は自身の関数 (前の層の前の層の関数) を呼び出す関数を表します。

*性質: 関数の環境。実際、環境はテーブルであり、関数はテーブル内のフィールド、または関数本体で自身が定義した変数にのみアクセスするように制限されます。

setfenv の例

--[[
    例 1: setfenv(1, env1) は、
    現在の関数の動作環境を設定します。
    次の例は、test1 関数の動作環境を env1 に設定するのと同じで、env1 は空であるため、print("test1 func") はエラーを報告してください]
]

local env1 = { } print("main func") local test1 = function()     setfenv(1, env1)     print("test1 func") end test1()
    






--[[
    正しい書き込み
    出力:
    main func
    test1 func
]]
local env1 = {     print = print } print("main func") local test1 = function()     setfenv(1, env1)     print("test1 func") end test1 ()








--[[
    例 2: setfenv(2, env1) は、
    setfenv 関数の上位層の関数環境を設定します
    。 次の例は、test1 関数の上位層の test2 関数の環境 env1 を設定するのと同等です。 running print("test2 func") エラーが報告されます
]]
local env1 = { } print("main func") local test1 = function()     setfenv(2, env1)     print("test1 func") end local test2 = function()     test1()     print("test2 func") end test2() --[[     print と test1 が test2 関数内で交換されてもエラーは報告されません]] local env1 = { } print("main func")ローカル test1 = function()     setfenv(2,env1)     print("test1 func") end local test2 = function()
    















    







    print("test2 func")
    test1()
end
test2()

 getfenv の例

--[[
例: getfenv(2)
test1 関数の環境 env1 は独自の動作環境を設定しているため、エラーが報告されますが、メイン関数test2 の関数は
正常に出力できます]] local env1 = { } print("main func") local test1 = function() local env2 = getfenv(2) setfenv(1, env2) print("test1 func") end local test2 = function() print("test2 func") setfenv(1, env1) test1() end test2 ()


















おすすめ

転載: blog.csdn.net/qq_41286356/article/details/118057795