「参照渡し」というフレーズは、CおよびC ++開発者によって同様に使用されますが、異なる意味で使用されているように見えます。各言語でのこのあいまいなフレーズの違いは正確には何ですか?
1 に答える
参照による受け渡しと値による受け渡しの違いをすでに扱っている質問があります。本質的に、引数を値で関数に渡すことは、関数が引数の独自のコピーを持つことを意味します-その値がコピーされます。そのコピーを変更しても、元のオブジェクトは変更されません。ただし、参照で渡す場合、関数内のパラメーターは渡されたものと同じオブジェクトを参照します。関数内の変更はすべて外部に表示されます。
残念ながら、「値渡し」と「参照渡し」というフレーズを使用する方法は2つあり、混乱を招く可能性があります。これが、特にCのバックグラウンドから来た場合に、新しいC++プログラマーがポインターと参照を採用するのが難しい理由の一部だと思います。
C
Cでは、すべてが技術的な意味で値によって渡されます。つまり、関数の引数として指定したものはすべて、その関数にコピーされます。たとえば、で関数void foo(int)
を呼び出すと、の値がのパラメータとしてfoo(x)
コピーされます。これは簡単な例で見ることができます:x
foo
void foo(int param) { param++; }
int main()
{
int x = 5;
foo(x);
printf("%d\n",x); // x == 5
}
の値がx
にコピーされfoo
、そのコピーがインクリメントされます。x
inmain
は引き続き元の値を持ちます。
ご存知のとおり、オブジェクトはポインタ型にすることができます。たとえば、をへのポインタとしてint* p
定義します。次のコードは2つのオブジェクトを導入することに注意することが重要です。p
int
int x = 5;
int* p = &x;
1つ目はタイプint
で、値はです5
。2番目はタイプint*
であり、その値は最初のオブジェクトのアドレスです。
関数へのポインターを渡すときは、それでも値で渡します。含まれているアドレスが関数にコピーされます。関数内でそのポインターを変更しても、関数外のポインターは変更されません。ただし、ポインターが指すオブジェクトを変更すると、関数外のオブジェクトが変更されます。しかし、なぜ?
同じ値を持つ2つのポインタは常に同じオブジェクトを指している(それらは同じアドレスを含んでいる)ので、指し示されているオブジェクトは両方を介してアクセスおよび変更される可能性があります。これにより、参照によってポイントされたオブジェクトを渡したというセマンティクスが得られますが、実際には参照は存在しません。Cには参照がありません。変更された例を見てください。
void foo(int* param) { (*param)++; }
int main()
{
int x = 5;
foo(&x);
printf("%d\n",x); // x == 6
}
int*
を関数に渡すとき、int
それが指すのは「参照によって渡された」と言えますが、実際には、int
実際にはどこにも渡されませんでした。ポインタだけが関数にコピーされました。これにより、「値渡し」と「参照渡し」という口語的な1の意味が得られます。
この用語の使用法は、標準内の用語によって裏付けられています。ポインタ型がある場合、それが指している型は、参照型と呼ばれます。つまり、の参照タイプはint*
ですint
。
ポインタ型は、関数型、オブジェクト型、または参照型と呼ばれる不完全な型から派生する場合があります。
単項*
演算子(のように*p
)は、標準では間接参照として知られていますが、一般に、ポインターの間接参照としても知られています。これにより、Cの「参照渡し」の概念がさらに促進されます。
C ++
C ++は、Cの元の言語機能の多くを採用しました。その中にはポインターが含まれているため、この口語的な形式の「参照渡し」は引き続き使用*p
できp
ます。ただし、C ++にはCにはない機能、つまり参照を真に渡す機能が導入されているため、この用語を使用すると混乱を招きます。
アンパサンドが後に続くタイプは、参照タイプ2です。たとえば、int&
はへの参照int
です。参照型をとる関数に引数を渡すとき、オブジェクトは本当に参照によって渡されます。関係するポインタ、オブジェクトのコピー、何もありません。関数内の名前は、実際には渡されたものとまったく同じオブジェクトを指します。上記の例とは対照的に、次のようになります。
void foo(int& param) { param++; }
int main()
{
int x = 5;
foo(x);
std::cout << x << std::endl; // x == 6
}
これで、foo
関数には。への参照であるパラメーターがありますint
。ここで、を渡すときは、まったく同じオブジェクトを参照しますx
。param
インクリメントparam
はの値に目に見える変化がありx
、現在x
は値6になっています。
この例では、値によって何も渡されませんでした。何もコピーされませんでした。参照による受け渡しが実際には値によるポインタの受け渡しであったCとは異なり、C++では真に参照による受け渡しが可能です。
「参照渡し」という用語にはこの潜在的なあいまいさがあるため、参照型を使用している場合は、C++のコンテキストでのみ使用することをお勧めします。ポインタを渡す場合は、参照で渡すのではなく、値でポインタを渡します(つまり、もちろん、ポインタへの参照を渡す場合を除きます!などint*&
)。ただし、ポインタが使用されているときに「参照渡し」の使用に遭遇する可能性がありますが、少なくとも実際に何が起こっているかはわかっています。
他の言語
他のプログラミング言語はさらに事態を複雑にします。Javaなどの一部では、オブジェクトへの参照として知られているすべての変数(C ++の参照とは異なり、ポインターのようなもの)がありますが、これらの参照は値によって渡されます。したがって、参照によって関数に渡されているように見えても、実際に行っているのは、値によって関数に参照をコピーすることです。C ++での参照による受け渡しとのこの微妙な違いは、渡された参照に新しいオブジェクトを割り当てるときにわかります。
public void foo(Bar param) {
param.something();
param = new Bar();
}
Javaでこの関数を呼び出し、タイプのオブジェクトを渡すと、渡したのと同じオブジェクトでBar
への呼び出しparam.something()
が呼び出されます。これは、オブジェクトへの参照を渡したためです。ただし、に新しいBar
ものが割り当てられていてparam
も、関数外のオブジェクトは同じ古いオブジェクトのままです。新しいものは外からは決して見えません。これfoo
は、内部の参照が新しいオブジェクトに再割り当てされているためです。この種の参照の再割り当ては、C++参照では不可能です。
1「口語」とは、「参照渡し」のCの意味がC ++の意味よりも真実ではないことを示唆するものではありません。ただ、C ++には実際に参照型があり、実際に参照渡しをしているだけです。Cの意味は、実際に値を渡すものを抽象化したものです。
2もちろん、これらは左辺値参照であり、C++11にも右辺値参照があります。