サンプルコード
#include <iostream>
// 値渡し
int method1(int value){
return ++value;
}
// ポインタ渡し
void method2(int* value){
*value = 102;
}
// 参照渡し
void method3(int& value){
value = 103;
}
int main(){
int value = 100;
// 値渡し
printf("値渡し:");
printf("%d\n", method1(value));
// ポインタ渡し
printf("ポインタ渡し:");
method2(&value);
printf("%d\n", value);
// 参照渡し
printf("参照渡し:");
method3(value);
printf("%d\n", value);
}
※一応の前提として、Cではポインタ渡ししかできない。
C++ではポインタ渡しに加え、参照渡しも可能。
ポインタ渡しと参照渡しの共通点
呼び出し先関数で、呼び出し元の変数を変更できる。
ポインタ渡しと参照渡しの文法上の違い
ポインタ渡しの書き方
・呼び出し元関数からは、変数のアドレスを引数として渡す。method2(&value);
・呼び出し先関数の仮引数は、ポインタ型変数として宣言する。void method2(int* value){
・呼び出し先関数内でポインタの指す先の値にアクセスするために、間接参照演算子を使う。*value = 102;
参照渡しの書き方
・呼び出し元関数からは、アドレスではなく変数そのものを引数として渡す。method3(value);
※つまり値渡しと同じ書き方である。このため、関数の呼び出し元だけ見ても、値渡しか参照渡しかは判断できない。判断するには、呼び出し先の定義を見る必要がある。
・呼び出し先関数の仮引数は、参照型(&)として宣言する。void method3(int &value){
・間接参照演算子やアドレス演算子を使わずに、通常の変数と同じように、参照された変数を操作できる。value = 103;
ポインタ渡しに比べて、アドレス演算子や間接参照演算子、宣言子の記述が少なく済むため、書き方がわかりやすい。 参照渡しは、変数を関数で受け渡す際の「ポインタ」の記述を簡易的に行えるようにするための機構と言える。
ポインタ渡しと参照渡しの機能面での違い
nullptrが渡されるか?
参照渡しでは、関数に渡す引数として参照型変数を使用する。文法上、「参照」は必ず有効なオブジェクトや変数を指すように初期化する必要があり、無効な値(nullptr)は格納できない。
つまり、参照渡しでは、呼び出し側が参照を渡すときに「無効な状態(nullptr のようなもの)」は発生しないという前提が成り立つ。
そのため、参照渡しはポインタ渡しよりも安全である。
ただし、(ややこしいのだが)参照渡しであれば、呼び出し先関数に 「絶対に」nullptrが渡され得ない、ということではない。
例として、以下のコードでは、参照型変数ではなく、ポインタ型変数(の値)を渡している。↓
// 参照渡し
void method3(int& value){
value = 103;
}
int main(){
int* ptrValue = nullptr;
// 参照渡し
printf("参照渡し:");
method3(*ptrValue);
printf("%d\n", *ptrValue);
}
int* ptrValue = nullptr;
method3(*ptrValue);
これにより、実行時にvalue = 103;
の箇所でSegmentation faultが発生することになる。
呼び出し元メソッド側では、あくまで「ポインタ(の値)」ではなく、「参照」を渡すことで、無効な値(nullptr)が渡され得ないことを文法上保証できる。
まとめ
特徴 | ポインタ渡し | 参照渡し |
---|---|---|
引数の型 | ポインタ (int*) | 参照 (int&) |
渡す値 | 変数のアドレス (&value) | 変数そのもの (value) |
アクセス方法 | 間接参照演算子でアクセス | そのまま通常の変数としてアクセス |
可読性 | & や * が多く、複雑 | 値渡しとほぼ変わらず、簡素 |
nullptrが渡されるか? | nullptrを渡せる | 参照を渡す限り、nullptrは来ない |