1. ホーム
  2. スクリプト・コラム
  3. ルア

luaのマジックテーブルを徹底解説

2022-01-09 04:39:14

プリアンブル

最近、すごいWMを構成しようと思って、luaをざっと見てみました。勉強しているうちに、luaのテーブルの使い方にすっかりやられてしまいました。

テーブルを辞書や配列として使用したり、クロージャやモジュールを設定したり、オブジェクトやクラスのモデリングに使用したりと、Luaでは本当にどこにでもあるものです。

辞書

テーブルの最も基本的な役割は、辞書として使用されることである。そのキーとなる値は、nilを除くあらゆる型の値であり得る。

t={}
t[{}] = "table" -- key can be table
t[1] = "int" -- key can be an integer
t[1.1] = "double" -- key can be a decimal
t[function () end] = "function" -- key can be a function
t[true] = "Boolean" -- key can be a Boolean
t["abc"] = "String" -- key can be a string
t[io.stdout] = "userdata" -- key can be userdata
t[coroutine.create(function () end)] = "Thread" -- key can be thread

tableを辞書として使用する場合、pairs関数で反復処理することができます。

for k,v in pairs(t) do
 print(k,"->",v)
end

実行結果は.

1→イント
1.1 -> double
スレッド: 0x220bb08 -> スレッド
テーブルを使用します。0x220b670 -> テーブル
abc -> 文字列
ファイル (0x7f34a81ef5c0) -> ユーザーデータ
関数を使用します。0x220b340 -> function
true -> ブール値

また、結果を見ると、ペアを使った探索の順番はランダムであり、実際、同じ文を複数回実行すると異なる結果になることがわかります。

テーブルのキーとして最も一般的なのは、整数と文字列の2種類である。キーが文字列の場合、tableは構造体として使用することができる。また、t["field"]のような形式は、t.fieldと書くことができる。

配列

キーが整数の場合、テーブルを配列として使用することができる。そして、その配列は1から始まるインデックスを持つ配列であり、固定長を持たず、必要に応じて自動的に成長させることができる。

a = {}
for i=0,5 do -- note that here it is intentionally written so that i starts at 0
 a[i] = 0
end

tableを配列として使用する場合、長さ演算子#を使用すると配列の長さを取得することができる

print(#a)

その結果

5
/{br

luaは、配列aの中に5つの要素しかないと思っていることがわかります。ipairsを使って配列を繰り返し処理することができます。

for i,v in ipairs(a) do
 print(i,v)
end

その結果

1 0
/{br 2 0
3 0
4 0
5 0

この結果から、aのインデックス0は配列の要素とはみなされないことがわかります。したがって、luaの配列は1からインデックスされることが検証されます。

また、テーブルを配列として使用する場合、インデックスがバラバラだと長さの計算時におかしなことになるので注意が必要です #。

a = {}
for i=1,5 do
 a[i] = 0
end
a[8] = 0 -- although the index is not contiguous, the length is based on the maximum index
print(#a)
a[100] = 0 -- the index is not contiguous and the length is no longer based on the maximum index
print(#a)

という結果になります。

8
/{br 8

ipairsを使った配列の走査は、1からインデックスの区切りまでしか走査しませんが

for i,v in ipairs(a) do
 print(i,v)
end

という結果になります。

1 0
/{br 2 0
3 0
4 0
5 0

環境 (名前空間)

luaはすべてのグローバル/ローカル変数を通常のテーブルに保持し、一般にグローバルまたは何らかの関数(クロージャ)の環境と呼ばれる。

luaでは便宜上、初期のグローバル環境を作成する際に、このグローバル環境を参照するためにグローバル変数_Gを使用します。そのため、_G[varname]を使用すれば、手動で環境を設定しなくても、グローバル変数の値にアクセスすることができます。

for k,v in pairs(_G) do
 print(k,"->",v)
end

rawequal -> function: 0x41c2a0
require -> function: 0x1ea4e70
_VERSION -> Lua 5.3 {Lua 5.3
debug -> テーブル。0x1ea8ad0
文字列→テーブル 0x1ea74b0
xpcall -> function: 0x41c720
select -> function: 0x41bea0
パッケージ→テーブル 0x1ea4820
アサート→関数 0x41cc50
pcall -> function: 0x41cd10
next -> function: 0x41c450
tostring -> function: 0x41be70
_G→テーブル 0x1ea2b80
coroutine -> テーブル。0x1ea4ee0
unpack -> function: 0x424fa0
loadstring -> function: 0x41ca00
setmetatable -> function: 0x41c7e0
rawlen -> function: 0x41c250
bit32→テーブル。0x1ea8fc0
utf8 -> テーブル。0x1ea8650
math -> テーブル。0x1ea7770
collectgarbage -> function: 0x41c650
rawset -> function: 0x41c1b0
os -> テーブル。0x1ea6840
ペア→関数 0x41c950
arg -> テーブル。0x1ea9450
テーブル→テーブル 0x1ea5130
tonumber -> function: 0x41bf40
io -> テーブル。0x1ea5430
loadfile -> function: 0x41cb10
error -> function: 0x41c5c0
load→関数です。0x41ca00
print -> function: 0x41c2e0
dofile -> function: 0x41cbd0
rawget -> function: 0x41c200
タイプ→関数。0x41be10
getmetatable -> function: 0x41cb80
モジュール→関数。0x1ea4e00
ipairs -> function: 0x41c970

lua 5.2からは、_ENVという値を変更することで関数の環境を設定し(lua 5.1のsetfenvは5.2から非推奨)、その関数内の実行文が新しい環境のグローバル変数の値をルックアップすることが可能になっています。

a=1 -- a=1 in the global variable
local env={a=10,print=_G.print} -- a=10 in the new environment, and make sure the global print function is accessible
function f1()
 local _ENV=env
 print("in f1:a=",a)
 a=a*10 -- the change is to the value of a in the new environment
end

f1()
print("globally:a=",a)
print("env.a=",env.a)
in f1:a= 10
global:a= 1
env.a= 100

また、新しく作成されたクロージャは、それを作成した関数の環境を引き継ぎます

モジュール

luaのモジュールは、テーブルを返すことで、モジュールユーザーも利用することができます。このテーブルには、関数や定数など、そのモジュールでエクスポートされるすべてのものが含まれています。

モジュールを定義するための一般的なテンプレートは次のとおりです。

module(モジュール名, package.seeall)

ここで module(モジュール名) は次のようなものです。

ローカル modname = モジュール名
local M = {} -- Mは、モジュールのすべての関数と定数が格納されているテーブルです。
G[modname] = M
package.loaded[modname] = M
setmetatable(M,{__index=_G}) -- package.seeall はグローバル環境 _G を現在の環境から見えるようにすることができます。
local _ENV = M -- 現在の実行環境をMに設定し、それ以降のすべてのコードでモジュール名を修飾する必要がなくなり、定義されたすべての関数が自動的にMのメンバになるようにします。

return M -- このモジュール関数は、モジュールテーブルを手動で返す代わりに、あなたに代わってモジュールテーブルを返します。

オブジェクト

luaでtableがオブジェクトとして使えるのは、次のような理由がある。

luaでは関数は値のクラスであり、テーブルの中の関数の値に直接アクセスすることができます。これにより、テーブルが独自の状態と独自の動作の両方を持つことができるのです。


Account = {balance = 0}
function Account.withdraw(v)
 Account.balance = Account.balance - v
end

luaはクロージャをサポートしており、オブジェクトのプライベートメンバー変数をモデル化するために使用できる機能です。

function new_account(b)
 local balance = b
 return {withdraw = function (v) balance = balance -v end,
  get_balance = function () return balance end
 }
end

a1 = new_account(1000)
a1.withdraw(10)
print(a1.get_balance())

990

しかし、上記の最初のオブジェクトの定義方法には、Accountという名前に死守されるという欠陥があります。つまり、オブジェクトの名前はAccoutでなければならず、そうでなければエラーが発生するのです

a = Account
Account = nil
a.withdraw(10) -- will report an error because Accout.balance no longer exists

この問題を解決するには、 withdraw メソッドに追加の引数を与えて、オブジェクトそのものを指すようにします。

Account = {balance=100}
function Account.withdraw(self,v)
 self.balance = self.balance - v
end
a = Account
Account = nil
a.withdraw(a,10) -- no problem, this time self is pointing to a, so it will look for a.balance
print(a.balance)

90

しかし、最初の引数であるselfはほとんどの場合、メソッドを呼び出すオブジェクト自身を指しているので、luaはself引数の定義と渡しを隠すために、object:method(...)という形で構文解析を提供します。このコロンには2つの役割があります。関数を定義する際に、その引数の1つの位置に追加の隠し引数sefを追加し、関数を呼び出す際に、その引数の1つの位置に追加の隠し引数selfを渡しています。つまり function object:method(v) end は次のものと同じです。 function object.method(self,v) end, object:method(v) と同等です。 {コード

クラス

クラスと継承に関しては、メタテーブルとメタメソッドが使用されます。実は、luaにはオブジェクトとクラスという厳密な区分はありません。

あるオブジェクトを他のテーブルの __index メタメソッドで参照すると、そのオブジェクトに定義されているメソッドをテーブルが参照できるため、そのオブジェクトはテーブルのクラスになると理解されます。

クラス定義の一般的なテンプレートは次のとおりです。

object.method(object,v)

または

function classname:new(o)
 o = o or {}
 setmetatable(o,{__index = self})
 return o
end

これに対して、2番目の書き方では、もう1つのテーブルを省略することができます

もう一つ、luaのメタメソッドはオブジェクト自体ではなく、メタテーブルで定義されている点が他のオブジェクト指向言語と異なる点だと思います。

概要

上記はこの記事のすべての内容です、私はあなたの勉強や仕事のためのこの記事の内容は、特定の参照学習価値があることを願って、あなたが交換するメッセージを残すことができます質問がある場合は、BinaryDevelopのあなたのサポートに感謝します。