web-dev-qa-db-ja.com

関数/変数スコープ(値または参照による受け渡し?)

Luaの変数スコープと関数引数の受け渡し(値または参照)に完全に混乱しています。

以下のコードを参照してください。

local a = 9        -- since it's define local, should not have func scope
local t = {4,6}    -- since it's define local, should not have func scope

function moda(a)
  a = 10           -- creates a global var?
end
function modt(t)
  t[1] = 7         -- create a global var?
  t[2] = 8
end

moda(a)
modt(t)
print(a)  -- print 9 (function does not modify the parent variable)
print(t[1]..t[2])  -- print 78 (some how modt is modifying the parent t var) 

そのため、この動作は私を完全に混乱させます。

  • これは、テーブル変数が値ではなく参照によって関数に渡されることを意味しますか?

  • グローバル変数の作成は、すでに定義されているローカル変数とどのように競合しますか?

    • なぜmodtはテーブルを変更できるのに、modaは変数を変更できないのですか?
30
frooyo

ご想像のとおり、テーブル変数は参照渡しされます。引用 Lua 5.1リファレンスマニュアル

Luaには、nil、boolean、number、string、function、userdata、thread、およびtableの8つの基本タイプがあります。 ....

テーブル、関数、スレッド、および(完全な)ユーザーデータ値はオブジェクトです。変数には実際にはこれらの値は含まれず、それらへの参照のみが含まれます。割り当て、パラメーターの受け渡し、および関数の戻り値は、常にそのような値への参照を操作します。これらの操作は、いかなる種類のコピーも意味しません。

したがって、nil、boolean、number、stringは値で渡されます。これは、観察する動作を正確に説明しています。

43
Bas Bossink

Luaのfunctiontableuserdataおよびthread(コルーチン)型は参照によって渡されます。他のタイプは値で渡されます。または、一部の人々がそれを好むように。すべての型は値で渡されますが、functiontableuserdata、およびthreadは参照型です。

stringも一種の参照型ですが、不変、インターン、およびコピーオンライトです。値型のように動作しますが、パフォーマンスは向上します。

ここで何が起こっているのですか:

local a = 9
local t = {4,6}

function moda(a)
  a = 10 -- sets 'a', which is a local introduced in the parameter list
end

function modt(t)
  t[1] = 7 -- modifies the table referred to by the local 't' introduced in the parameter list
  t[2] = 8
end

おそらく、これにより、物事がなぜそうであるのかについて、物事を見通しに入れることができます:

local a = 9
local t = {4,6}

function moda()
  a = 10 -- modifies the upvalue 'a'
end

function modt()
  t[1] = 7 -- modifies the table referred to by the upvalue 't'
  t[2] = 8
end

-- 'moda' and 'modt' are closures already containing 'a' and 't',
-- so we don't have to pass any parameters to modify those variables
moda()
modt()
print(a)  -- now print 10
print(t[1]..t[2])  -- still print 78
20
jA_cOp

「すべての型は値によって渡されますが、関数、テーブル、ユーザーデータ、およびスレッドは参照型です」と言うとき、jA_cOpは正しいです。

これと「テーブルは参照渡し」の違いは重要です。

この場合、違いはありませんが、

function modt_1(x)
  x.foo = "bar"
end

結果:「テーブルを参照で渡す」と「テーブルを値で渡すが、テーブルは参照型である」の両方で同じことが行われます。xのfooフィールドは「bar」に設定されます。

しかし、この機能のために、それは違いの世界を作ります

function modt_2(x)
  x = {}
end

この場合、参照渡しでは、引数が空のテーブルに変更されます。ただし、「値による受け渡しですが、参照型」では、新しいテーブルはローカルにxにバインドされ、引数は変更されません。 luaでこれを試してみると、2番目(値は参照)であることがわかります。

19

Bas Bossinkと参照タイプに関するjA_cOpの回答で既に述べたことを繰り返しませんが、

-ローカル定義であるため、funcスコープを使用しないでください

これは間違っています。 Luaの変数は lexically scoped です。つまり、コードのブロックとそのすべてのネストされたブロックで定義されます。
localが行うことは、ステートメントが存在するブロック、関数の本体、「インデントのレベル」、またはファイルのいずれかに制限される新しい変数を作成することです。

つまり、変数を参照するたびに、Luaはその変数がローカルで宣言されているコードブロックが見つかるまで「上方向にスキャン」し、そのような宣言がない場合はデフォルトでグローバルスコープになります。

この場合、atはローカルで宣言されていますが、宣言はグローバルスコープ内にあるため、atはグローバルです。または最大で、現在のファイルに対してローカルです。

これらは関数内でlocalで再宣言されません、しかしパラメーターとして宣言され、同じ効果があります。それらが関数パラメーターでない場合、関数本体内部の参照は外部の変数を参照します。

Lua-users.orgには Scopeチュートリアル があり、説明での私の試み以上に役立ついくつかの例があります。 主題に関するLuaのセクションでのプログラミング も良い読み物です。

7
Zecc

これは、テーブル変数が値ではなく参照によって関数に渡されることを意味しますか?

はい。

グローバル変数の作成は、すでに定義されているローカル変数とどのように競合しますか?

そうではありません。 tというグローバル変数があり、tという引数を持つ関数に渡すため、2つのtsが異なるため、そのように見えるかもしれません。引数の名前をqなどの別の名前に変更すると、出力はまったく同じになります。 modt(t)は、グローバル変数tを変更できます。これは、参照で渡すためです。たとえば、modt({})を呼び出す場合、グローバルtは影響を受けません。

Modtがテーブルを変更できるのに、modaは変数を変更できないのはなぜですか?

引数はローカルだからです。引数の名前付けaは、引数が渡された値を受け取り、通常のローカル変数が受け取らないことを除いて、local aでローカル変数を宣言するのに似ています。引数がzと呼ばれた(またはまったく存在しなかった)場合、modaは実際にグローバルaを変更します。

1
finnw