1. ホーム
  2. c++

[解決済み] C言語とC++の「参照渡し」は具体的にどう違うのでしょうか?

2022-03-04 17:28:48

質問

参照渡しと言う言葉はCとC++の開発者が同じように使っていますが、それぞれ違う意味で使われているようです。それぞれの言語におけるこの等閑視されたフレーズの違いは一体何なのでしょうか?

どのように解決するのですか?

をすでに扱っている質問もあります。 参照渡しと値渡しの違いについて . 要するに、関数に引数を値で渡すということは、その関数が引数のコピーを持つということです - その がコピーされます。そのコピーを変更しても、元のオブジェクトは変更されません。しかし、参照渡しの場合、関数内のパラメータは を参照しています。 関数内部での変更は外部にも反映されます。

残念ながら、quot;pass by valueとquot;pass by referenceの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 で、そのコピーがインクリメントされる。その xmain は元の値を持ち続けます。

ご存知のように、オブジェクトはポインタ型にすることができます。例えば int* p が定義する p へのポインタとして使用します。 int . 以下のコードでは、2つのオブジェクトを導入していることに注意する必要があります。

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 .

A ポインタ型 は、関数型、オブジェクト型、あるいは不完全な という型があります。 参照型 .

一方、単項演算子 * 演算子(例えば *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*& ). しかし、ポインタが使われているときに、quot; pass by reference"を使うことがあるかもしれませんが、これで少なくとも何が実際に起こっているかはわかりましたね。


その他の言語

他のプログラミング言語では、さらに複雑なことが起こります。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言語の意味は、実際の値による受け渡しを抽象化したものです。

2 もちろん、これらはlvalueの参照であり、C++11ではrvalueの参照もある。