language-agnostic - 英語 - 戻り 値 参照 渡し




参照渡しまたは値渡し? (8)

PHPも値渡しです。

<?php
class Holder {
    private $value;

    public function __construct($value) {
        $this->value = $value;
    }

    public function getValue() {
        return $this->value;
    }
}

function swap($x, $y) {
    $tmp = $x;
    $x = $y;
    $y = $tmp;
}

$a = new Holder('a');
$b = new Holder('b');
swap($a, $b);

echo $a->getValue() . ", " . $b->getValue() . "\n";

出力:

a b

しかし、PHP4ではオブジェクトは primitives ように扱われました。 つまり:

<?php
$myData = new Holder('this should be replaced');

function replaceWithGreeting($holder) {
    $myData->setValue('hello');
}

replaceWithGreeting($myData);
echo $myData->getValue(); // Prints out "this should be replaced"

新しいプログラミング言語を学習する場合、発生する可能性のある障害の1つは、言語がデフォルトで 値渡しか参照渡し かという質問です。

あなたの好きな言語での皆さんへの私の質問はここにあります。 そして、 可能な落とし穴は 何ですか?

もちろん、あなたの好きな言語は、これまでに遊んだことのあるものであれば何でもかまいません。


Perlの回答はまだ見たことがありませんので、書きたいと思いました。

内部では、Perlは参照渡しとして効果的に機能します。 関数呼び出し引数としての変数は参照で渡され、定数は読み取り専用値として渡され、式の結果は一時的なものとして渡されます。 @_ からのリスト割り当てまたは shift による引数リストを作成する通常のイディオムは、これをユーザーから隠す傾向があり、値渡しのように見えます:

sub incr {
  my ( $x ) = @_;
  $x++;
}

my $value = 1;
incr($value);
say "Value is now $value";

$x++ は、渡された変数ではなく、 incr() 関数内で宣言されたレキシカル変数をインクリメントしているため、 Value is now 1 が出力されます。この値渡しスタイルは、通常、関数Perlで引数を変更することはまれであり、スタイルは避ける必要があります。

ただし、何らかの理由でこの動作が特に必要な場合は、関数に渡される変数のエイリアスになるため、 @_ 配列の要素を直接操作することで実現できます。

sub incr {
  $_[0]++;
}

my $value = 1;
incr($value);
say "Value is now $value";

$_[0]++ 式が実際の $value 変数をインクリメントしたため、今回は Value is now 2 になります。 これが機能する方法は my @array @_ は他のほとんどの配列( my @array によって取得されるような)のような実際の配列ではなく、その要素が関数呼び出しに渡された引数から直接構築されることです。 これにより、必要に応じて参照渡しのセマンティクスを構築できます。 プレーン変数である関数呼び出し引数はそのままこの配列に挿入され、定数またはより複雑な式の結果は読み取り専用の一時として挿入されます。

ただし、Perlが参照値をサポートしているため、実際にこれを行うことは非常にまれです。 つまり、他の変数を参照する値。 通常、変数への参照を渡すことにより、変数に明らかな副作用を持つ関数を作成する方がはるかに明確です。 これは、コールサイトでの読者への明確な指示であり、参照渡しのセマンティクスが有効であることを示しています。

sub incr_ref {
  my ( $ref ) = @_;
  $$ref++;
}

my $value = 1;
incr(\$value);
say "Value is now $value";

ここで、 \ 演算子は、Cの & アドレス演算子とほぼ同じ方法で参照を生成します。


値渡しまたは参照渡しとして何を言っても、言語間で一貫している必要があります。 言語間で使用される最も一般的で一貫性のある定義は、参照渡しでは、変数を「通常」関数に渡すことができる(つまり、アドレスなどを明示的に取得することなく)、関数が関数内のパラメーターの内容。呼び出し元スコープの変数に割り当てるのと同じ効果があります。

このビューから、言語は次のようにグループ化されます。 各グループは同じ受け渡しセマンティクスを持ちます。 2つの言語を同じグループに入れるべきではないと思われる場合は、それらを区別する例を考えてみてください。

C Java Python Ruby JavaScript Scheme OCaml Standard ML Go Objective-C Smalltalk などを含む大部分の言語は、すべて 値渡し です。 ポインター値(一部の言語では「参照」と呼ばれます)を渡すことは、参照渡しとしてカウントされません。 指し示されているものではなく、渡されたもののみを懸念しています。

C ++ C# PHP などの言語は、上記の言語と同様にデフォルトで値渡しされますが、関数は & または ref を使用してパラメーターを明示的に参照渡しとして宣言できます。

Perl は常に参照渡しです。 ただし、実際には、ほとんどの場合、値は取得後に値をコピーするため、値渡し方法で使用されます。


ここ に.NET について 良い説明 があります。

多くの人は、参照オブジェクトが実際に値で渡されることに驚いています(C#とJavaの両方で)。 スタックアドレスのコピーです。 これにより、メソッドがオブジェクトが実際に指す場所を変更することを防ぎますが、メソッドはオブジェクトの値を変更できます。 C#では、参照ごとに参照を渡すことができます。つまり、実際のオブジェクトが指す場所を変更できます。


C#プログラミング言語の 別の記事を次に示し ます。

c#は引数 を値で 渡し ます (デフォルト)

private void swap(string a, string b) {
  string tmp = a;
  a = b;
  b = tmp;
}

したがって、このバージョンのスワップを呼び出しても結果はありません。

string x = "foo";
string y = "bar";
swap(x, y);

"output: 
x: foo
y: bar"

ただし、 java c# とは異なり 、参照によって パラメーターを渡す機会を開発者に与えるため、パラメーターのタイプの前に 'ref'キーワードを使用してこれを行います。

private void swap(ref string a, ref string b) {
  string tmp = a;
  a = b;
  b = tmp;
} 

このスワップにより、参照されるパラメーターの値 変更されます。

string x = "foo";
string y = "bar";
swap(x, y);

"output: 
x: bar
y: foo"

c#には outキーワード もあり、refとoutの違いは微妙です。 msdnから:

outパラメーター を取るメソッドの呼び出し元は、呼び出しの前にoutパラメーターとして渡される変数に割り当てる必要はありません。 ただし、呼び出し先は、 戻る前にoutパラメーターに割り当てる必要 あります。

そして

対照的に、 refパラメーター は、呼び出し先によって 最初に割り当てられ と見なされ ます 。 そのため、呼び出し先は使用前に ref パラメーターに 割り当てる必要 はあり ません 。 Refパラメーターは、メソッドの内外に渡されます。

Javaの場合と同様に、小さな落とし穴は、 値によって渡されるオブジェクトが内部メソッドを使用して変更できることです。

結論:

  • c#は、デフォルトで によってパラメーターを渡し ます
  • ただし、必要な場合は、refキーワードを使用し て参照によって パラメーターを渡すこともできます。
  • 値によって渡されるパラメーターからの内部メソッドはオブジェクト を変更 します(そのメソッド自体が値を変更する場合)

便利なリンク:


J については、値で渡すAFAIKしかありませんが、参照で渡す形式があり、多くのデータを移動できます。 ロケールと呼ばれるものを動詞(または関数)に渡すだけです。 クラスのインスタンスまたは単なる汎用コンテナにできます。

spaceused=: [: 7!:5 <
exectime =: 6!:2
big_chunk_of_data =. i. 1000 1000 100
passbyvalue =: 3 : 0
    $ y
    ''
)
locale =. cocreate''
big_chunk_of_data__locale =. big_chunk_of_data
passbyreference =: 3 : 0
    l =. y
    $ big_chunk_of_data__l
    ''
)
exectime 'passbyvalue big_chunk_of_data'
   0.00205586720663967
exectime 'passbyreference locale'
   8.57957102144893e_6

明らかな欠点は、呼び出された関数で何らかの方法で変数の名前を知る必要があることです。 しかし、この手法は多くのデータを簡単に移動できます。 そのため、技術的には参照渡しではありませんが、私はそれを「ほとんど」と呼んでいます。


name によるパスとvalue-resultによるパス もあることを忘れないでください。

値渡しの結果は、値渡しと似ていますが、パラメータとして渡された元の変数に値が設定されるという側面が追加されています。 グローバル変数との干渉をある程度回避できます。 参照渡しがページフォールト( Reference )を引き起こす可能性があるパーティションメモリでは、明らかに優れています。

名前渡しは、値がプロシージャの開始時ではなく、実際に使用されたときにのみ計算されることを意味します。 Algolは名前によるパスを使用していましたが、興味深い副作用として、スワッププロシージャを記述することが非常に困難です( Reference )。 また、名前で渡される式は、アクセスされるたびに再評価されますが、これも副作用を引き起こす可能性があります。


値で

  • システムがパラメータをコピーする必要があるため、参照よりも遅い
  • 入力のみに使用

参照により

  • ポインタのみが渡されるため、高速
  • 入出力に使用
  • グローバル変数と組み合わせて使用​​すると非常に危険です




pass-by-value