参照はエイリアスと考えてください。参照で何かを呼び出すとき、実際には参照が参照するオブジェクトでそれを呼び出しています。
int i;
int& j = i; // j is an alias to i
j = 5; // same as i = 5
関数に関しては、次の点を考慮してください。
void foo(int i)
{
i = 5;
}
上記のint i
は値であり、渡される引数はvalue によって渡されます。つまり、次のように言えます。
int x = 2;
foo(x);
i
のコピーになりx
ます。したがってi
、5 に設定しても、変更x
のコピーであるため、 には影響しませんx
。ただし、i
参照を作成すると、次のようになります。
void foo(int& i) // i is an alias for a variable
{
i = 5;
}
次に、 no と言うと、 ;foo(x)
のコピーが作成されなくなりました。です。つまり、関数内は とまったく同じで、変化します。x
i
x
foo(x)
i = 5;
x = 5;
x
うまくいけば、それは少し明確になります。
何でこれが大切ですか?プログラミングするときは、コードをコピーして貼り付けたくありません。1 つのタスクを実行する関数を作成し、それをうまく実行したいとします。そのタスクを実行する必要があるときはいつでも、その機能を使用します。
では、2 つの変数を交換したいとしましょう。それは次のようになります。
int x, y;
// swap:
int temp = x; // store the value of x
x = y; // make x equal to y
y = temp; // make y equal to the old value of x
わかりました。これを関数にしたいのは、次の理由からですswap(x, y);
。それでは、これを試してみましょう:
void swap(int x, int y)
{
int temp = x;
x = y;
y = temp;
}
これはうまくいきません!問題は、これが2 つの変数のコピーを交換していることです。あれは:
int a, b;
swap(a, b); // hm, x and y are copies of a and b...a and b remain unchanged
参照が存在しない C では、解決策はこれらの変数のアドレスを渡すことでした。つまり、ポインター* を使用します。
void swap(int* x, int* y)
{
int temp = *x;
*x = *y;
*y = temp;
}
int a, b;
swap(&a, &b);
これはうまくいきます。ただし、使い方が少し不器用で、実際には少し安全ではありません。swap(nullptr, nullptr)
、2 つの何もないものを交換し、null ポインターを逆参照します...未定義の動作です! いくつかのチェックで修正可能:
void swap(int* x, int* y)
{
if (x == nullptr || y == nullptr)
return; // one is null; this is a meaningless operation
int temp = *x;
*x = *y;
*y = temp;
}
しかし、私たちのコードがどれほどぎこちなくなっているように見えます。C++ では、この問題を解決するための参照が導入されています。変数にエイリアスを付けるだけで、探していたコードが得られます。
void swap(int& x, int& y)
{
int temp = x;
x = y;
y = temp;
}
int a, b;
swap(a, b); // inside, x and y are really a and b
使いやすさと安全性を両立。(誤って null を渡すことはできません。null 参照はありません。) これが機能するのは、関数内で発生するスワップが、関数の外部でエイリアス化されている変数で実際に発生しているためです。
(注意、決してswap
関数を書かないでください。:) 1 つは既に header<algorithm>
に存在し、任意の型で動作するようにテンプレート化されています。)
もう 1 つの用途は、関数を呼び出したときに発生するコピーを削除することです。非常に大きなデータ型があるとします。このオブジェクトのコピーには多くの時間がかかりますが、それは避けたいと思います:
struct big_data
{ char data[9999999]; }; // big!
void do_something(big_data data);
big_data d;
do_something(d); // ouch, making a copy of all that data :<
ただし、本当に必要なのは変数のエイリアスだけなので、それを示しましょう。(繰り返しますが、C に戻って、ビッグ データ型のアドレスを渡し、コピーの問題を解決しますが、不器用になります。):
void do_something(big_data& data);
big_data d;
do_something(d); // no copies at all! data aliases d within the function
これが、プリミティブ型でない限り、常に参照によって渡す必要があると言われる理由です。(内部的にエイリアスを渡すことは、おそらく C のようにポインターで行われるためです。小さなオブジェクトの場合は、コピーを作成してからポインターを心配する方が高速です。)
const-rect である必要があることに注意してください。これは、関数がパラメーターを変更しない場合、それを としてマークすることを意味しますconst
。do_something
上記のように を見ただけで変化しなかった場合は、次のようdata
にマークしますconst
。
void do_something(const big_data& data); // alias a big_data, and don't change it
私たちはコピーを避け、「これは変更しません」と言います。これには他の副作用 (一時変数など) がありますが、今は心配する必要はありません。
対照的に、実際にエイリアスを変更しているため、swap
関数を にすることはできません。const
これでさらに明確になることを願っています。
*大まかなポインタのチュートリアル:
ポインターは、別の変数のアドレスを保持する変数です。例えば:
int i; // normal int
int* p; // points to an integer (is not an integer!)
p = &i; // &i means "address of i". p is pointing to i
*p = 2; // *p means "dereference p". that is, this goes to the int
// pointed to by p (i), and sets it to 2.
したがって、ポインター バージョン スワップ関数を見たことがある場合は、スワップする変数のアドレスを渡し、逆参照して値を取得および設定してスワップを実行します。