🤖
【C++】ポインタと参照の違い・値渡しと参照渡し・関数ポインタ・スマートポインター
ポインタ:アドレスを格納する変数
- ポインタ=アドレス(メモリ空間上の位置)を格納する変数
- ポインタ変数も
int
型やchar
型などの基本型の変数同様に、変数宣言を行う際に、その変数用のメモリが確保され、メモリ空間上に配置される。しかし、変数宣言直後は、ポインタには不定値が格納されており、どこを指しているか分からない状態。 - 変数名の前に
&
記号をつけることで、その変数のアドレスを取得できる。 - デリファレンス(逆参照):ポインタ変数の前に
*
(間接演算子)を付けることで、このポインタに格納されたアドレスのデータにアクセスできる。
詳細はこちら:https://daeudaeu.com/pointer/
ポインタ変数宣言
int *p; // ポインタ変数宣言:int型を変数を指すポインタ
// * は変数名側に寄せて付けても良いし、型名側に寄せてつけてもコンパイルは通る
int* p; //int *p;と同じ
下記では、x
は 'A'
で初期化されているので、ptr
は x
のアドレスを指し、*ptr
(ptr
が指す値)は 'A'
になる。
char x = 'A';
char *ptr;
ptr = &x;
printf("*ptr = %c\n", *ptr); // *ptr = A"
下記の場合、x
は初期化されていない。したがって、x
に格納されている値は不定であり、*ptr
の結果も未定義(undefined)。
char x;
char *ptr;
ptr = &x;
printf("*ptr = %c\n", *ptr);
// ポインタ変数宣言時点では pNumberには具体的なメモリアドレスが割り当てられていない
int* pNumber;
// error. プログラムはポインタが指す不定の場所に10を書き込もうとする
*pNumber = 10;
ポインタ変数を宣言する際には、有効なメモリアドレスを割り当てるか、nullptr
や NULL
で初期化することが推奨される。直接具体的なアドレス値を代入することは、特殊な状況を除いて避けるべき。
// プログラムのメモリ領域に number という変数を作成し、その変数が 10 という値で初期化
int number = 10;
// number変数のメモリアドレスは、& 演算子を使って取得→これはOK
int* p = &number;
*pNumber = 41;
// ポインタ変数宣言と初期化
//非推奨:ポインタpにリテラルアドレス 0x0000 を代入しているが、このアドレスが有効である保証はない
int* p = 0x0000;
// NULL に初期化することは有効。これはポインタがどこも指さないことを明示的に示す
int* p = nullptr; // C++11 以降
ポインタと参照の違い
特徴 | ポインタ | 参照 |
---|---|---|
定義 | ポインタが指し示すアドレスに格納されている値にアクセスする操作。 Windows のショートカットや Linux のシンボリックリンクと同じような機能。 | 参照自体がデリファレンスされた値として振る舞う。 |
初期化 | 初期化せずに宣言可能。nullptrに初期化可能。 | 宣言時に必ず初期化する必要がある。 |
再代入 | 異なるアドレスを指し示すように変更可能。 | 一度初期化すると、別のオブジェクトを参照するように変更不可。 |
'null' 状態 | nullptrを指し示すことができる。 | 'null'参照は存在しない。 |
値にアクセス | デリファレンス(逆参照)(*)を使ってアクセス。 | 直接アクセス可能。 |
メモリアドレス | 明示的にアドレス(&)を取得して操作。 | アドレスを直接扱わない。 |
操作性 | アドレス算術が可能。 | 提供される操作が限定的。 |
安全性 | ポインタは誤った使い方をすると危険。 | 参照はより安全で、未初期化の状態がない。 |
使用例 | 動的なメモリ管理、配列操作など。 | 関数の引数や戻り値、オブジェクトのエイリアスなど。 |
値渡しと参照渡し
値渡し(pass by value)
- 値渡しの場合、関数に引数として渡されるのは変数の値のコピー
- そのため、関数内で引数の値を変更しても、それはコピーされた値に対する変更であるため、関数外の元の変数には影響しない。
- 関数で行った変更を関数外の変数に反映させたい場合は、returnメソッドで関数から値を戻り値として返し、それを外の変数に再代入する必要がある。ただし、
return
で渡せるデータは1つなので、関数が呼び出し元に渡せる結果は1つのデータのみ。
void increment(int value) {
value = value + 1;
}
int x = 5;
increment(x); // この時点でのxの値はまだ5。
int increment(int value) {
return value + 1; //returnで、関数から値を戻り値として返す
}
int x = 5;
x = increment(x); // この時点でのxの値は6になる。
参照渡し(pass by reference)
- 参照渡しの場合、関数に引数として渡されるのは変数のアドレス(または参照)。
- 関数実行時に引数として渡されたデータの複製が作成されるのは同じだが、ポインタの場合はポインタに格納されたアドレスが複製される。したがって、ポインタ変数としては全く別のものでも、関数呼び出しにより複製されたポインタも複製元のポインタが指している場所を同様に指す。
- このため、関数内で引数を通じて値を変更すると、その変更はメモリ上の同じアドレスに保存されている値に対して行われるため、関数外の変数に直接反映される。
- この場合、関数外の変数を更新するために再代入する必要はない。
C++で参照渡しを実現するには、2つの方法がある
1)引数としてポインタを使用する
void increment(int* value) {
*value = *value + 1;
}
int x = 5;
increment(&x); // x=6
2)参照を使用する
void increment(int& value) {
value = value + 1;
}
int x = 5;
increment(x); // この時点でのxの値は6になる。
コピー時間の短縮によるプログラムの高速化
値渡しの場合
- 引数の型によって、、
続きは、こちらで記載しています。
Discussion