回答:
何よりもまず、CS理論で定義されていた「値渡しと参照渡し」の区別は、元々「参照渡し」として定義されていた手法が支持されなくなったため、現在はほとんど使用されていないため、廃止されました。1
新しい言語2は、異なる(ただし類似した)技術のペアを使用して、混乱の主な原因である同じ効果(以下を参照)を達成する傾向があります。
「参照渡し」では、「参照」は一般的な用語「参照」よりも狭い意味を持っているという事実があります(そのフレーズがそれより前にあるため)。
さて、本物の定義は:
パラメーターが参照渡しされる場合、呼び出し元と呼び出し先はパラメーターに同じ変数を使用します。呼び出し先がパラメータ変数を変更した場合、その効果は呼び出し元の変数に表示されます。
パラメータが値によって渡される場合、呼び出し元と呼び出し先には、同じ値を持つ2つの独立した変数があります。呼び出し先がパラメータ変数を変更した場合、その効果は呼び出し元には見えません。
この定義で注意すべき点は次のとおりです。
ここでの「変数」とは、呼び出し元の(ローカルまたはグローバル)変数自体を意味します。つまり、ローカル変数を参照で渡し、それに割り当てた場合、呼び出し元の変数自体を変更します。たとえば、ポインターの場合、それが指しているものは変更しません。 。
「参照渡し」における「参照」の意味。一般的な「参照」という用語との違いは、この「参照」は一時的で暗黙的なものであることです。呼び出し先が基本的に取得するのは、元の変数と何とか「同じ」「変数」です。この効果が具体的にどのように達成されるかは関係ありません(たとえば、言語は実装の詳細(アドレス、ポインター、逆参照)を公開する場合もあります。これはすべて関係ありません。これが実質的な影響である場合、参照渡しです)。
現在、現代の言語では、変数は「参照型」(「参照渡し」より後に発明され、それから着想を得たもの)になる傾向があります。つまり、実際のオブジェクトデータはどこかに(通常はヒープ上に)個別に格納されます。それへの「参照」だけが変数に保持され、パラメータとして渡されます。3
変数の値は技術的には参照自体であり、参照されるオブジェクトではないため、このような参照を渡すことは値渡しに該当します。ただし、プログラムへの最終的な影響は、値渡しまたは参照渡しと同じです。
ご覧のとおり、この2つの手法は定義の手法とほとんど同じですが、間接参照のレベルのみが異なります。「変数」を「参照オブジェクト」に置き換えるだけです。
それらには合意された名前はありません。これは、「値が参照である場合の値による呼び出し」などの説明が歪む原因になります。1975年に、Barbara Liskovは「オブジェクト共有による呼び出し」(または単に「呼び出しによる共有」という場合もある)という用語を提案しました。さらに、これらのフレーズはどちらも元のペアとの類似点を示していません。古い用語が何もない状態で再利用されて混乱に終わったのも不思議ではありません。4
注:長い間、この答えは次のように言っていました。
あなたとウェブページを共有したいとします。URLを教えていただければ、参照で渡します。そのURLを使用して、私と同じWebページを表示できます。そのページが変更された場合、両方の変更が表示されます。URLを削除すると、そのページへの参照が破棄されるだけで、実際のページ自体は削除されません。
ページを印刷して印刷すると、値渡しになります。あなたのページはオリジナルの切り離されたコピーです。その後の変更は表示されず、加えた変更(印刷物の落書きなど)は元のページには表示されません。プリントアウトを破棄すると、実際にはオブジェクトのコピーが破棄されますが、元のWebページはそのまま残ります。
これは、「参照」のより狭い意味を除いてほとんど正しいです-一時的および暗黙的の両方です(必須ではありませんが、明示的および/または永続的であることが追加機能であり、参照渡しセマンティクスの一部ではありません) 、上記で説明したように)。よく似ているのは、ドキュメントのコピーを提供するのではなく、オリジナルに取り組むように招待するのです。
1 FortranまたはVisual Basicでプログラミングしている場合を除き、これはデフォルトの動作ではありません。また、最近のほとんどの言語では、真の参照渡しは不可能です。
2 古いものもかなりサポートしています
3 いくつかの現代の言語では、すべての型が参照型です。このアプローチは、1975年にCLU言語によって開拓され、それ以降、PythonやRubyなどの他の多くの言語で採用されています。さらに、より多くの言語がハイブリッドアプローチを使用しており、一部のタイプは「値タイプ」であり、その他のタイプは「参照タイプ」です。その中にはC#、Java、JavaScriptがあります。
4 適切な古い用語自体をリサイクルしても何も悪いことはありませんが、毎回どの意味が使用されているかを何らかの方法で明確にする必要があります。そうしないと、混乱を引き起こし続けます。
関数に引数を渡す方法です。参照渡しとは、呼び出された関数のパラメーターが呼び出し元の渡された引数と同じであることを意味します(値ではなくID-変数自体)。値渡しは、呼び出された関数のパラメーターが呼び出し元の渡された引数のコピーになることを意味します。値は同じですが、ID(変数)は異なります。したがって、呼び出された関数によって行われるパラメータを変更すると、渡された引数が変更され、呼び出された関数(コピーのみ)のパラメータの値が変更されるだけです。急いで:
ref
呼び出し元と呼び出し先の関数で使用されるキーワード)をサポートしています。ジョン・スキートも、これについて良い説明をしています。コード
私の言語はC ++なので、ここで使用します
// passes a pointer (called reference in java) to an integer
void call_by_value(int *p) { // :1
p = NULL;
}
// passes an integer
void call_by_value(int p) { // :2
p = 42;
}
// passes an integer by reference
void call_by_reference(int & p) { // :3
p = 42;
}
// this is the java style of passing references. NULL is called "null" there.
void call_by_value_special(int *p) { // :4
*p = 10; // changes what p points to ("what p references" in java)
// only changes the value of the parameter, but *not* of
// the argument passed by the caller. thus it's pass-by-value:
p = NULL;
}
int main() {
int value = 10;
int * pointer = &value;
call_by_value(pointer); // :1
assert(pointer == &value); // pointer was copied
call_by_value(value); // :2
assert(value == 10); // value was copied
call_by_reference(value); // :3
assert(value == 42); // value was passed by reference
call_by_value_special(pointer); // :4
// pointer was copied but what pointer references was changed.
assert(value == 10 && pointer == &value);
}
また、Javaの例は問題ありません。
class Example {
int value = 0;
// similar to :4 case in the c++ example
static void accept_reference(Example e) { // :1
e.value++; // will change the referenced object
e = null; // will only change the parameter
}
// similar to the :2 case in the c++ example
static void accept_primitive(int v) { // :2
v++; // will only change the parameter
}
public static void main(String... args) {
int value = 0;
Example ref = new Example(); // reference
// note what we pass is the reference, not the object. we can't
// pass objects. The reference is copied (pass-by-value).
accept_reference(ref); // :1
assert ref != null && ref.value == 1;
// the primitive int variable is copied
accept_primitive(value); // :2
assert value == 0;
}
}
ウィキペディア
http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_value
http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_reference
この男はそれをかなり釘付けにします:
ここでの多くの回答(特に、最も賛成された回答)は、「参照による呼び出し」が実際に何を意味するのかを誤解しているため、実際には正しくありません。ここに、問題を正直にしようとする私の試みがあります。
最も簡単な言葉で:
比喩的に言えば:
これらの概念は両方とも、参照型の概念(Javaではのサブタイプであるすべての型Object
であり、C#ではすべてのclass
型です)またはCのようなポインター型の概念(意味的に同等)から完全に独立しており、直交していることに注意してくださいJavaの「参照型」に、異なる構文で)。
参照型の概念はURLに対応します。それ自体が情報の一部であり、他の情報への参照(ポインタ)でもあります。URLの多くのコピーをさまざまな場所に置くことができ、リンク先のWebサイトは変更されません。Webサイトが更新された場合でも、すべてのURLコピーは更新された情報につながります。逆に、いずれか1つの場所でURLを変更しても、URLの他の書かれたコピーには影響しません。
C ++は、「参照」(例えばの概念があることに注意してくださいint&
)ではない JavaとC#の"参照型"のような、しかしである『参照による呼び出し』のように。JavaとC#の「参照型」、およびPythonのすべての型は、CおよびC ++が「ポインタ型」と呼ぶもの(たとえばint*
)に似ています。
わかりました、これはより長くより正式な説明です。
まず、いくつかの重要な用語を強調し、私の答えを明確にし、単語を使用するときに全員が同じアイデアを参照していることを確認します。(実際には、これらのトピックに関する混乱の大部分は、意図された意味を完全に伝えない方法で単語を使用することに起因すると考えています。)
まず、C言語のような言語での関数宣言の例を次に示します。
void foo(int param) { // line 1
param += 1;
}
そして、これはこの関数を呼び出す例です:
void bar() {
int arg = 1; // line 2
foo(arg); // line 3
}
この例を使用して、いくつかの重要な用語を定義します。
foo
1行目で宣言された関数です(Javaはすべての関数をメソッドにすることを主張していますが、概念は一般性を失うことなく同じです; CとC ++はここでは触れない宣言と定義を区別します)param
ある仮パラメータにfoo
もライン上で宣言し、1arg
ある変数、具体的にはローカル変数関数のbar
宣言と行で初期化は、2arg
また、ある引数の特定のへの呼び出しのfoo
行に3ここで区別する2つの非常に重要な概念のセットがあります。1つ目は、値と変数の比較です。
bar
上記の関数では、行のint arg = 1;
後の式arg
に値 があります1
。final
やC#を使用して宣言されるなどreadonly
)、または非常に不変(C ++を使用する場合などconst
)にすることができます。区別するための他の重要な概念のペアは、パラメータと引数です。
で値によって呼び出し、関数の仮パラメータは、新たに関数呼び出しのために作成されており、どれがで初期化されている変数です値、引数のは。
これは、他の種類の変数が値で初期化されるのとまったく同じように機能します。例えば:
int arg = 1;
int another_variable = arg;
ここarg
でanother_variable
、完全に独立した変数です-それらの値は互いに独立して変更できます。ただし、another_variable
が宣言された時点で、と同じ値をarg
保持するように初期化され1
ます。
これらは独立した変数であるため、変更によるanother_variable
影響はありませんarg
。
int arg = 1;
int another_variable = arg;
another_variable = 2;
assert arg == 1; // true
assert another_variable == 2; // true
これは、上記の例arg
との関係とまったく同じparam
です。対称性についてここで繰り返します。
void foo(int param) {
param += 1;
}
void bar() {
int arg = 1;
foo(arg);
}
これは、コードを次のように記述した場合とまったく同じです。
// entering function "bar" here
int arg = 1;
// entering function "foo" here
int param = arg;
param += 1;
// exiting function "foo" here
// exiting function "bar" here
つまり、値による呼び出しが何を意味するかを定義する特性は、呼び出し先(foo
この場合)は値を引数として受け取りますが、呼び出し元の変数(これらの場合)からのそれらの値に対する独自の変数を持っているということですbar
。
上記の比喩に戻って、私bar
とあなたがそうfoo
である場合、私があなたを呼ぶとき、私はあなたにその上に価値が書かれた紙を手渡します。あなたはその一枚の紙を呼びますparam
。その値は、ノートブック(ローカル変数)で、変数に書き込んだ値のコピーですarg
。
(余談ですが、ハードウェアとオペレーティングシステムによっては、ある関数を別の関数から呼び出す方法についてさまざまな呼び出し規約があります。この呼び出し規約は、私が紙に値を書き込んでそれをあなたに渡すかどうかを決定するようなものです、または私が書いた紙がある場合、または私たち2人の前の壁に書いた場合。これも興味深い主題ですが、この長い回答の範囲をはるかに超えています。)
で参照することにより呼び出し、関数の仮パラメータは、単純にある新しい名前を引数として呼び出し側用品、同じ変数について。
上記の例に戻ると、これは次と同等です。
// entering function "bar" here
int arg = 1;
// entering function "foo" here
// aha! I note that "param" is just another name for "arg"
arg /* param */ += 1;
// exiting function "foo" here
// exiting function "bar" here
以来param
のためのちょうど別の名前であるarg
-である、彼らは同じ変数に対する変更param
に反映されていますarg
。これは、参照渡しが値渡しと異なる基本的な方法です。
参照による呼び出しをサポートする言語はほとんどありませんが、C ++では次のようにできます。
void foo(int& param) {
param += 1;
}
void bar() {
int arg = 1;
foo(arg);
}
この場合、param
はと同じ値を持つだけでなく、arg
実際には arg
(単に別の名前で)なので、インクリメントされているbar
ことを確認できarg
ます。
これは、Java、JavaScript、C、Objective-C、Python、または今日のほとんどの他の一般的な言語が機能する方法ではないことに注意してください。つまり、これらの言語は参照渡しではなく、値渡しです。
あなたが持っているものが値による呼び出しであるが、実際の値が参照型またはポインタ型である場合、「値」自体はそれほど興味深いものではありません(たとえば、Cではプラットフォーム固有のサイズの単なる整数です)。興味深いのは、その値が指すものです。
その参照型(つまり、ポインター)が指しているものが変更可能である場合、興味深い効果が得られます。つまり、ポイント先の値を変更でき、呼び出し元は監視できなくても、ポイント先の値への変更を監視できます。ポインタ自体に変わります。
再びURLの類推を借りると、私があなたにWebサイトへのURLのコピーを提供したという事実は、私たち2人がURLではなくWebサイトである場合、特に興味深いものではありません。URLのコピーを上書きしても、私のURLのコピーには影響しないという事実は、私たちが気にすることではありません(実際、JavaやPythonなどの言語では、「URL」、つまり参照型の値によって、まったく変更しないでください。それによってポイントされたものだけが変更できます)。
Barbara Liskovは、CLUプログラミング言語(これらのセマンティクスを持つ)を発明したとき、既存の用語「値による呼び出し」および「参照による呼び出し」は、この新しい言語のセマンティクスを説明するのに特に有用ではないことに気付きました。そこで、彼女は新しい用語を考案しました。オブジェクト共有による呼び出しです。
技術的に値で呼び出されているが、一般的に使用されている型が参照型またはポインタ型(つまり、ほとんどすべての現代の命令型、オブジェクト指向型、またはマルチパラダイムプログラミング言語)である言語について説明するときは、混乱が少ないことがわかります。値による呼び出しや参照による呼び出しについて話すのは避けてください。スティックするオブジェクト共有によって呼び出す(あるいは単にオブジェクトで呼び出す)と誰もが混同されません。:-)
The first is value versus variable.
The other important pair of concepts to distinguish is parameter versus argument:
2つの用語を理解する前に、次のことを理解する必要があります。すべてのオブジェクトには、それを区別できる2つのものが含まれています。
だからあなたが言うなら employee.name = "John"
について2つのことを知っていますname
。その値と"John"
、メモリ内の16進数の位置は、次のようになります0x7fd5d258dd00
。
言語のアーキテクチャまたはオブジェクトのタイプ(クラス、構造体など)に応じて、転送する"John"
か、0x7fd5d258dd00
受け渡し"John"
は、値渡しと呼ばれます。渡すこと0x7fd5d258dd00
は、参照渡しと呼ばれます。このメモリ位置を指している人は誰でもの値にアクセスできます"John"
。
詳細については、ポインターの逆参照について、およびクラス(参照型)ではなく構造体(値型)を選択する理由をお読みになることをお勧めします。
次に例を示します。
#include <iostream>
void by_val(int arg) { arg += 2; }
void by_ref(int&arg) { arg += 2; }
int main()
{
int x = 0;
by_val(x); std::cout << x << std::endl; // prints 0
by_ref(x); std::cout << x << std::endl; // prints 2
int y = 0;
by_ref(y); std::cout << y << std::endl; // prints 2
by_val(y); std::cout << y << std::endl; // prints 2
}
y
前の行ですでに2に設定されています。なぜ0に戻るのですか?
これを取得する最も簡単な方法は、Excelファイルです。たとえば、セルA1とB1に2つの数値5と2があり、それらの合計を3番目のセルで見つけたいとします。たとえば、A2とします。これには2つの方法があります。
このセルに= 5 + 2と入力してセルA2に値を渡すか、この場合、セルA1またはB1の値が変化しても、A2の合計は同じままです。
または、= A1 + B1と入力して、セルA1およびB1の「参照」をセルA2に渡します。この場合、セルA1またはB1の値が変化すると、A2の合計も変化します。
値渡し-関数は変数をコピーし、コピーを操作します(元の変数の内容は変更されません)
参照渡し-関数は元の変数を使用します。他の関数で変数を変更すると、元の変数も変更されます。
例(コピーして使用/試してみてください):
#include <iostream>
using namespace std;
void funct1(int a){ //pass-by-value
a = 6; //now "a" is 6 only in funct1, but not in main or anywhere else
}
void funct2(int &a){ //pass-by-reference
a = 7; //now "a" is 7 both in funct2, main and everywhere else it'll be used
}
int main()
{
int a = 5;
funct1(a);
cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 5
funct2(a);
cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 7
return 0;
}
シンプルにしてください。テキストの壁は悪い習慣になり得ます。
それらの主な違いは、値型変数は値を格納するため、メソッド呼び出しで値型変数を指定すると、その変数の値のコピーがメソッドに渡されます。参照型変数はオブジェクトへの参照を格納するため、参照型変数を引数として指定すると、オブジェクトを参照する実際の参照のコピーがメソッドに渡されます。参照自体は値で渡されますが、メソッドは受け取った参照を使用して、元のオブジェクトと対話し、場合によっては変更します。同様に、returnステートメントを介してメソッドから情報を返す場合、メソッドは値型変数に格納されている値のコピー、または参照型変数に格納されている参照のコピーを返します。参照が返されると、呼び出し側のメソッドはその参照を使用して、参照されるオブジェクトと対話できます。そう、
C#では、呼び出されたメソッドが変数を変更できるように参照によって変数を渡すために、C#はキーワードrefおよびoutを提供します。パラメータ宣言にrefキーワードを適用すると、参照によってメソッドに変数を渡すことができます。呼び出されたメソッドは、呼び出し元の元の変数を変更できます。refキーワードは、呼び出しメソッドですでに初期化されている変数に使用されます。通常、メソッド呼び出しに初期化されていない変数が引数として含まれている場合、コンパイラーはエラーを生成します。パラメータの前にキーワードoutを指定すると、出力パラメータが作成されます。これは、引数が参照によって呼び出されたメソッドに渡され、呼び出されたメソッドが呼び出し元の元の変数に値を割り当てることをコンパイラーに示します。メソッドが実行のすべての可能なパスで出力パラメーターに値を割り当てない場合、コンパイラーはエラーを生成します。これにより、コンパイラが、メソッドに引数として渡される初期化されていない変数のエラーメッセージを生成することも防止されます。メソッドはreturnステートメントを介して呼び出し元に値を1つだけ返すことができますが、複数の出力(refおよび/またはout)パラメータを指定することで多くの値を返すことができます。
C#のディスカッションと例をここにリンクテキストを参照してください
次に、値渡し-ポインタ値-参照の違いを示す例を示します。
void swap_by_value(int a, int b){
int temp;
temp = a;
a = b;
b = temp;
}
void swap_by_pointer(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void swap_by_reference(int &a, int &b){
int temp;
temp = a;
a = b;
b = temp;
}
int main(void){
int arg1 = 1, arg2 = 2;
swap_by_value(arg1, arg2);
cout << arg1 << " " << arg2 << endl; //prints 1 2
swap_by_pointer(&arg1, &arg2);
cout << arg1 << " " << arg2 << endl; //prints 2 1
arg1 = 1; //reset values
arg2 = 2;
swap_by_reference(arg1, arg2);
cout << arg1 << " " << arg2 << endl; //prints 2 1
}
「参照渡し」方式には重要な制限があります。パラメータが参照渡しとして宣言されている場合(そのため&記号が前に付いている)、対応する実際のパラメータは変数である必要があります。
「値で渡される」仮パラメーターを参照する実際のパラメーターは、一般に式である可能性があるため、変数だけでなく、リテラルまたは関数呼び出しの結果を使用することもできます。
関数は、変数以外の何かに値を置くことができません。リテラルに新しい値を割り当てたり、式で結果を強制的に変更したりすることはできません。
PS:わかりやすい言葉で説明している現在のスレッドでDylan Beattieの回答を確認することもできます。