値渡し と 参照渡し の違い
「値渡し」と「参照渡し」はプログラミングにおいて、メソッドや関数への引数の渡し方の種類を表す用語です。
今回は「値渡し」と「参照渡し」のそれぞれの仕組みをC++で紹介します。
※用語として、メソッド/関数の引数は仮引数、メソッド/関数を呼び出す際に渡す変数は実引数といいます。
[目次]
1. 値渡し
値渡しは実引数の値を仮引数にコピーして渡す方法です。
下記の例ではhogeという関数の引数 a と b に、変数 x と y の値をコピーした値が渡されます。
void hoge(int a, int b) { std::cout << "[hoge]_before\n"; std::cout << "a=" + std::to_string(a) + "\n"; std::cout << "b=" + std::to_string(b) + "\n\n"; a += 1; // 引数aに1を足す b *= 2; // 引数bに2を掛ける std::cout << "[hoge]_after\n"; std::cout << "a=" + std::to_string(a) + "\n"; std::cout << "b=" + std::to_string(b) + "\n\n"; } int main() { int x = 10; int y = 5; std::cout << "[main]_befor\n"; std::cout << "x=" + std::to_string(x) + "\n"; std::cout << "y=" + std::to_string(y) + "\n\n"; hoge(x, y); std::cout << "[main]_after\n"; std::cout << "x=" + std::to_string(x) + "\n"; std::cout << "y=" + std::to_string(y) + "\n\n"; }
実行結果は次の通りです。
[main]_befor x=10 y=5 [hoge]_before a=10 b=5 [hoge]_after a=11 b=10 [main]_after x=10 y=5
関数hogeの中では引数 a と b に 、mainから渡された値 10 と 5 のコピーが渡されます。([hoge]_before)
hogeの中では渡された引数を使って正しい計算が行われていることがわかります。([hoge]_after)
しかし、関数hogeからmainに戻ってきて変数 x と y の値を確認すると、値が変わっていないことがわかります。([main]_after)
このことから、値渡しでは「仮引数を変更しても実引数には反映されない」ということがわかります。
2. 参照渡し
参照渡しは仮引数が、実引数のメモリ領域を指し示すようになります。
下記の例ではhogeという関数の引数 a は、変数 x と同じメモリ領域を指し示し、引数 b は、変数 y と同じメモリ領域を指し示しめしています。
void hoge(int& a, int& b) { std::cout << "[hoge]_before\n"; std::cout << "a=" + std::to_string(a) + "\n"; std::cout << "b=" + std::to_string(b) + "\n\n"; a += 1; // 引数aに1を足す b *= 2; // 引数bに2を掛ける std::cout << "[hoge]_after\n"; std::cout << "a=" + std::to_string(a) + "\n"; std::cout << "b=" + std::to_string(b) + "\n\n"; } int main() { int x = 10; int y = 5; std::cout << "[main]_befor\n"; std::cout << "x=" + std::to_string(x) + "\n"; std::cout << "y=" + std::to_string(y) + "\n\n"; hoge(x, y); std::cout << "[main]_after\n"; std::cout << "x=" + std::to_string(x) + "\n"; std::cout << "y=" + std::to_string(y) + "\n\n"; }
実行結果は次の通りです。
[main]_befor x=10 y=5 [hoge]_before a=10 b=5 [hoge]_after a=11 b=10 [main]_after x=11 y=10
関数hogeの引数 a と b は、実引数 x と y のメモリ領域を指し示すため、それぞれ 10 と 5 が表示されています。([hoge]_before)
hogeの中計算が行われると、a と b の値は更新されます。([hoge]_after)
関数hogeからmainに戻ってきたとき変数 x と y の値を確認すると、値が更新されていることがわかります。([main]_after)
このことから、参照渡しでは「仮引数を変更すると、実引数にも反映される」ということがわかります。
3. (おまけ) ポインタ渡し
C言語とC++にはポインタ渡し(アドレス渡し)という渡し方があります。これは、実引数が指し示すアドレスを仮引数にコピーして渡す方法です。
ポインタ渡しを使用した例は次のコードです。
void hoge(int* a, int* b) { std::cout << "[hoge]_before\n"; std::cout << "a=" + std::to_string(*a) + "\n"; std::cout << "b=" + std::to_string(*b) + "\n\n"; *a += 1; // 引数aに1を足す *b *= 2; // 引数bに2を掛ける std::cout << "[hoge]_after\n"; std::cout << "a=" + std::to_string(*a) + "\n"; std::cout << "b=" + std::to_string(*b) + "\n\n"; } int main() { int x = 10; int y = 5; std::cout << "[main]_befor\n"; std::cout << "x=" + std::to_string(x) + "\n"; std::cout << "y=" + std::to_string(y) + "\n\n"; hoge(&x, &y); std::cout << "[main]_after\n"; std::cout << "x=" + std::to_string(x) + "\n"; std::cout << "y=" + std::to_string(y) + "\n\n"; }
mainの中で関数hogeに渡している実引数についている" & "はアドレス演算子、関数hogeの仮引数についている" * "はポインタ宣言子といいます。
実行結果は次の通りです。
[main]_befor x=10 y=5 [hoge]_before a=10 b=5 [hoge]_after a=11 b=10 [main]_after x=11 y=10
参照渡しと同様に、「仮引数を変更すると、実引数にも反映される」ということがわかります。
C++ではポインタ渡しと参照渡しの使い分けとして、値がnullになる可能性があるものはポインタ渡しを使い、それ以外は参照渡しを使います。(参照渡しにnullを渡すことはできないため。)
よって、ポインタ渡しで渡ってきたときは、nullチェックを実施するという運用が必要です。
bool foo(int* a) { // aに対してNULLチェックを実施 if (a == nullptr) { return false; } *a += 1; // 引数aに1を足す return true; } int main() { int x = 10; foo(&x); std::cout << "x=" + std::to_string(x) + "\n"; }
4. まとめ
「値渡し」と「参照渡し」について紹介しました。言語によって多少差はありますが、基本的な考え方は同じなのでぜひ覚えておいてください。