違いは何ですか
- 参照によって渡されたパラメーター
- 値によって渡されるパラメータ?
例をいくつか教えてください。
違いは何ですか
例をいくつか教えてください。
何よりもまず、CS 理論で定義されている「値による受け渡しと参照による受け渡し」の区別は現在では廃止されています。これは、もともと「参照による受け渡し」として定義されていた手法が支持されなくなり、現在ではほとんど使用されていないためです。1
新しい言語2は、同じ効果(以下を参照) を達成するために、異なる (しかし類似した) 手法のペアを使用する傾向があり、これが混乱の主な原因です。
混乱の二次的な原因は、「参照渡し」では、「参照」が一般的な用語「参照」よりも狭い意味を持っているという事実です(句がそれより前にあるため)。
さて、本物の定義は次のとおりです。
パラメーターが参照によって渡される場合、呼び出し元と呼び出し先はパラメーターに同じ変数を使用します。呼び出し先がパラメーター変数を変更すると、その効果は呼び出し元の変数に表示されます。
パラメーターが値によって渡される場合、呼び出し元と呼び出し先は同じ値を持つ2 つの独立した変数を持ちます。呼び出し先がパラメーター変数を変更した場合、その効果は呼び出し元には表示されません。
この定義で注意すべき点は次のとおりです。
ここでの「変数」とは、呼び出し元の (ローカルまたはグローバル) 変数自体を意味します。つまり、参照によってローカル変数を渡して代入すると、呼び出し元の変数自体が変更されます。たとえば、それがポインターである場合は、それが指しているものは何でも変更されます。 .
「参照渡し」における「参照」の意味。一般的な「参照」用語との違いは、この「参照」が一時的で暗黙的であることです。呼び出し先が基本的に取得するのは、元の変数と「同じ」である「変数」です。この効果が具体的にどのように達成されるかは関係ありません (たとえば、言語はいくつかの実装の詳細 (アドレス、ポインター、参照解除など) も公開する可能性があります。これはすべて無関係です。正味の効果がこれである場合、それは参照渡しです)。
現在、現代の言語では、変数は「参照型」 (「参照渡し」より後に発明され、それに触発された別の概念) である傾向があります。つまり、実際のオブジェクト データは別の場所 (通常はヒープ上) に格納されます。それへの「参照」のみが変数に保持され、パラメーターとして渡されます。3
このような参照を渡すことは、値渡しに分類されます。変数の値は、技術的には、参照されるオブジェクトではなく、参照自体であるためです。ただし、プログラムに対する最終的な影響は、値渡しまたは参照渡しと同じになる可能性があります。
お分かりのように、この一対のテクニックは定義のテクニックとほぼ同じですが、間接的なレベルがあるだけで、「変数」を「参照されるオブジェクト」に置き換えるだけです。
それらには合意された名前がないため、「値が参照である値による呼び出し」などの歪んだ説明につながります。1975 年、Barbara Liskov は「call-by-object-sharing」(または単に「call-by-sharing」) という用語を提案しましたが、まったく普及することはありませんでした。さらに、これらのフレーズはどちらも元のペアとは類似していません。より良いものがないのに古い用語が再利用され、混乱を招いたのも不思議ではありません。4
注:長い間、この回答は次のように言っていました:
Web ページをあなたと共有したいとします。URLをお伝えする場合は、参照渡しです。その URL を使用して、私が見ているのと同じ Web ページを表示できます。そのページが変更された場合、私たちは両方とも変更を確認します。URL を削除すると、そのページへの参照が破棄されるだけで、実際のページ自体は削除されません。
ページを印刷してあなたに渡す場合、私は値渡しをしています。あなたのページはオリジナルの分離されたコピーです。その後の変更は表示されず、行った変更 (印刷物への落書きなど) は元のページには表示されません。印刷物を破棄すると、実際にはオブジェクトのコピーが破棄されますが、元の Web ページはそのまま残ります。
これは、「参照」の狭義の意味を除いて、ほとんど正しいです-- それは一時的かつ暗黙的です (必須ではありませんが、明示的および/または永続的であることは追加機能であり、参照渡しのセマンティックの一部ではありません)。 、上で説明したように)。より近い類推は、ドキュメントのコピーを提供することと、元のドキュメントで作業するように招待することです。
1 Fortran や Visual Basic でプログラミングしている場合を除き、これはデフォルトの動作ではなく、最近使用されているほとんどの言語では、真の参照渡しは不可能です。
2かなりの数の高齢者も支持している
3いくつかの現代言語では、すべての型が参照型です。このアプローチは、1975 年に言語 CLU によって開発され、それ以来、Python や Ruby を含む他の多くの言語で採用されています。さらに多くの言語がハイブリッド アプローチを使用しており、一部の型は "値型" であり、他の型は "参照型" です。その中には、C#、Java、および JavaScript があります。
4適切な古い用語を再利用すること自体は悪いことではありませんが、毎回どの意味が使用されているかを何らかの方法で明確にする必要があります。それをしないことが、まさに混乱を引き起こし続けているのです。
これは、引数を関数に渡す方法です。参照による受け渡しは、呼び出された関数のパラメーターが呼び出し元の渡された引数と同じになることを意味します(値ではなく、ID-変数自体)。値渡しとは、呼び出された関数のパラメーターが、呼び出し元の渡された引数のコピーになることを意味します。値は同じになりますが、ID(変数)は異なります。したがって、呼び出された関数によって行われたパラメーターを変更すると、渡された引数が変更され、別の場合は、呼び出された関数(コピーのみ)のパラメーターの値が変更されます。急いで:
ref
呼び出し元で使用されるキーワードと呼び出される関数)をサポートしています。Jon Skeetも、これについての良い説明をここに持っています。コード
私の言語は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 コピーは引き続き更新された情報につながります。逆に、どこかで URL を変更しても、他の URL のコピーには影響しません。
C++ には、 Java や C# の「参照型」とは異なり、「参照による呼び出し」に似た「参照」(例: int&
)の概念があることに注意してください。Java と C# の「参照型」、およびPython のすべての型は、C と C++ が「ポインター型」と呼ぶものに似ています (例: )。int*
OK、これがより長く、より正式な説明です。
まず、いくつかの重要な用語を強調して、私の答えを明確にし、言葉を使用するときにすべて同じ考えを参照していることを確認したいと思います. (実際には、このようなトピックに関する混乱の大部分は、意図した意味を完全に伝えない方法で単語を使用することに起因すると考えています。)
まず、関数宣言の 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
あり、1 行目でも宣言されていますarg
変数、具体的には関数のローカル変数であり、2bar
行目で宣言および初期化されますarg
3行目の特定の呼び出しに対する引数でもありますfoo
ここで区別すべき 2 つの非常に重要な概念のセットがあります。最初はvalueとvariable です:
bar
上記の関数では、行の後int arg = 1;
に式arg
の値 1
が含まれています。final
や C#を使用して宣言されたreadonly
もの)、または完全に不変 (C++ を使用したものなどconst
) にすることができます。区別すべきもう 1 つの重要な概念のペアは、parameterとargumentです。
値による呼び出しでは、関数の仮パラメーターは、関数呼び出し用に新しく作成され、引数の値で初期化される変数です。
これは、他の種類の変数が値で初期化されるのとまったく同じように機能します。例えば:
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
(余談ですが、ハードウェアやオペレーティング システムによっては、ある関数を別の関数から呼び出す方法について、さまざまな呼び出し規則があります。呼び出し規則は、紙に値を書いてそれを渡すかどうかを決めるようなものです。 、または私が書いた紙を持っている場合、または私たち二人の前の壁に書いている場合. これも興味深い主題ですが、このすでに長い回答の範囲をはるかに超えています.)
参照による呼び出しでは、関数の仮パラメーターは、呼び出し元が引数として提供する同じ変数の単なる新しい名前です。
上記の例に戻ると、次と同等です。
// 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 のコピーをあなたに渡したという事実は、私たちが気にかけているのが URL ではなく Web サイトである場合、特に興味深いものではありません。URL のコピーに落書きしても私の URL のコピーに影響しないという事実は、私たちが気にすることではありません (実際、Java や Python などの言語では、「URL」または参照型の値は、はまったく変更できません。それが指すものだけが変更できます)。
Barbara Liskov は、CLU プログラミング言語 (これらのセマンティクスを持つ) を発明したとき、「値による呼び出し」と「参照による呼び出し」という既存の用語は、この新しい言語のセマンティクスを記述するのに特に有用ではないことに気付きました。そこで彼女は新しい用語を発明しました:オブジェクト共有による呼び出しです。
技術的には値によって呼び出されるが、使用される一般的な型が参照型またはポインター型 (つまり、ほとんどすべての現代の命令型、オブジェクト指向、またはマルチパラダイムのプログラミング言語) である言語について議論するとき、私は、値による呼び出しまたは参照による呼び出しについて話すのは避けてください。オブジェクト共有による呼び出し(または単にobject による呼び出し) に固執すれば、誰も混乱することはありません。:-)
2 つの用語を理解する前に、次のことを理解する必要があります。すべてのオブジェクトには、それを区別できる 2 つの要素があります。
だからあなたが言うならemployee.name = "John"
については 2 つのことがわかっていname
ます。16 進数であるメモリ内の値と"John"
その位置は、おそらく次のようになります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
}
これを取得する最も簡単な方法は、Excel ファイルを使用することです。たとえば、セル A1 と B1 に 5 と 2 の 2 つの数値があり、3 番目のセル (A2 としましょう) でそれらの合計を求めたいとします。これには 2 つの方法があります。
このセルに= 5 + 2と入力して、値をセル A2に渡します。この場合、セル A1 または B1 の値が変化しても、A2 の合計は同じままです。
または、= A1 + B1と入力して、セル A1 と B1 の「参照」をセル A2 に渡します。この場合、セル A1 または B1 の値が変化すると、A2 の合計も変化します。
refを渡すときは、基本的に変数へのポインターを渡します。変数のコピーを渡す値を渡します。基本的な使用法では、これは通常、参照を渡すことを意味します。変数への変更は呼び出し元のメソッドであり、値を渡すことはありません。
値渡しは、指定した変数に格納されているデータのコピーを送信し、参照渡しは、変数自体への直接リンクを送信します。したがって、参照によって変数を渡してから、渡したブロック内の変数を変更すると、元の変数が変更されます。単に値を渡す場合、元の変数は、渡したブロックによって変更することはできませんが、呼び出し時に含まれていたもののコピーを取得します。
値渡し - 関数は変数をコピーし、コピーで動作します (そのため、元の変数は何も変更されません)。
参照渡し - 関数は元の変数を使用します。他の関数で変数を変更すると、元の変数も変更されます。
例(コピーして使用/これを自分で試して見てください):
#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#のディスカッションと例を参照してくださいリンクテキスト
この写真を見てください:
最初のケース (参照渡し) では、変数が関数内で設定または変更されると、外部変数も変更されます。
しかし、2 番目のケース (値渡し) では、関数内の変数を変更しても外部変数には影響しません。
記事を読むには、このリンクを参照してください。
例:
class Dog
{
public:
barkAt( const std::string& pOtherDog ); // const reference
barkAt( std::string pOtherDog ); // value
};
const &
一般的に最適です。建設と破壊のペナルティは発生しません。参照がconstでない場合、インターフェイスは渡されたデータを変更することを示唆しています。
元の変数を関数に渡した後でその値を変更したくない場合は、「値渡し」パラメータを使用して関数を構築する必要があります。
次に、関数には値のみが含まれますが、渡された変数のアドレスは含まれません。変数のアドレスがないと、関数内のコードは、関数の外側から見た変数値を変更できません。
しかし、外部から見た変数の値を変更する機能を関数に与えたい場合は、参照渡しを使用する必要があります。値とアドレス (参照) の両方が渡され、関数内で使用できるためです。
値渡し - ポインター値 - 参照 の違いを示す例を次に示します。
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 の回答を平易な言葉で説明していることも確認できます。