C++入門|ポインタ渡しの基本

ポインタ渡しは、変数の住所を関数へ渡すC++の重要技。値だけでなく、呼び出し元の変数そのものを動かせる感覚を身につけよう。

前回までに、変数には値だけでなく、メモリ上の住所であるアドレスがあることを学びました。

たとえば、int型の変数 power があるとき、power は中に入っている値を表します。
一方、&power は、その変数がメモリ上のどこにあるかを表すアドレスです。

そして、そのアドレスを保存するための変数がポインタです。

ここから学ぶのは、そのポインタを関数の引数として渡す方法です。
これをポインタ渡しといいます。

ドラゴンボール風にたとえるなら、普通の値渡しは、戦士の戦闘力のコピーを修行装置に渡すようなものです。
コピーなので、修行装置の中で値を変えても、本物の戦士の戦闘力は変わりません。

一方、ポインタ渡しは、戦士本人がいる修行部屋の住所を修行装置に渡すようなものです。
住所を知っているので、修行装置はその部屋にアクセスし、本物の戦闘力を直接変更できます。

この違いが分かると、ポインタ渡しの役割がかなり見えてきます。

渡し方渡すもの関数内で呼び出し元の変数を変更できるかドラゴンボール風のイメージ
値渡し値のコピーできない戦闘力の写しを渡す
ポインタ渡し変数のアドレスできる戦士のいる部屋の住所を渡す

ポインタ渡しを使うと、関数の中から呼び出し元の変数を書き換えることができます。
そのため、2つの変数の値を入れ替えるような処理も作れます。

今回のテーマでは、まずポインタ変数を使うときの注意点を確認し、そのあとでポインタを関数に渡すサンプルを見ていきます。

図:値渡しとポインタ渡しの違い

この図が示していること

この図では、値渡しとポインタ渡しの違いを表しています。

値渡しでは、関数に渡されるのは値のコピーです。
そのため、関数の中で値を変更しても、呼び出し元の変数には影響しません。

ポインタ渡しでは、関数に変数のアドレスを渡します。
関数はそのアドレスをたどって元の変数にアクセスできるため、呼び出し元の変数そのものを変更できます。

ポインタ変数を使うときは型を合わせる

ポインタ変数にほかの変数のアドレスを入れるときは、変数の型とポインタの型を合わせることが大切です。

たとえば、int型の変数のアドレスを入れるなら、int型のポインタを使います。

int power;
int* p;
p = &power;

double型の変数のアドレスを入れるなら、double型のポインタを使います。

double speed;
double* pd;
pd = &speed;

ポインタは、ただアドレスを持つだけの変数に見えるかもしれません。
しかし、ポインタの型には大切な意味があります。

int* は、int型の値が置かれている場所を指すポインタです。
double* は、double型の値が置かれている場所を指すポインタです。

変数対応するポインタ適切ではない例
int powerint* pchar* p
double speeddouble* pdint* p
char markchar* pcdouble* pd

型が合っていないポインタを無理に使うと、メモリの読み方がずれてしまう危険があります。
C++では型チェックが厳しいため、型が違う代入はコンパイル時に止められることもあります。
また、無理な変換をして扱うと、実行時に危険な動作につながる可能性があります。

ドラゴンボール風にたとえるなら、int型の変数は整数用の修行カプセル、double型の変数は小数も扱える精密カプセルです。
整数用のカプセルを、小数用の読み取り装置で無理に読もうとすると、正しく扱えません。

ポインタは、指す先の型に合わせて使うことが大切です。

ポインタの型が大切な理由

ポインタは、アドレスを持つ変数です。
ただし、アドレスだけ分かればよいわけではありません。

ポインタは、次のような情報も持っています。

ポインタの型指す先読み取るサイズの考え方
int*int型の変数int型として読む
double*double型の変数double型として読む
char*char型の変数char型として読む

たとえば、int* のポインタであれば、指している先にはint型の値があるものとして扱います。
そのため、*p と書いたとき、C++はその場所にあるデータをint型として読み取ります。

ここで型が合っていないと、メモリの中身を正しく解釈できなくなる可能性があります。

ドラゴンボール風にいえば、ポインタの型はスカウターの読み取りモードです。

int* は整数戦闘力モード。
double* は精密戦闘力モード。
char* は文字コードモード。

読み取りモードを間違えると、同じ場所を見ていても意味の違うデータとして扱ってしまいます。

ポインタ渡しとは何か

ポインタ渡しとは、関数の引数に変数のアドレスを渡す方法です。

普通に関数へ値を渡す場合、関数側には値のコピーが渡されます。
そのため、関数の中で値を変更しても、呼び出し元の変数は変わりません。

一方、ポインタ渡しでは、関数側にアドレスを渡します。
関数はそのアドレスを使って、呼び出し元の変数そのものにアクセスできます。

たとえば、次のような関数を考えます。

void changePower(int* p) {
    *p = 200;
}

この関数に変数powerのアドレスを渡すと、関数の中でpowerの値を変更できます。

changePower(&power);

このとき、&power は power のアドレスです。
関数側では int* p として受け取り、*p を使って元の変数の値を書き換えます。

書き方意味
&powerpowerのアドレス
int* pint型変数のアドレスを受け取るポインタ
*ppが指している先の値
*p = 200pが指している元の変数へ200を代入する

ポインタ渡しは、関数から呼び出し元の変数を変更したいときに使える重要な仕組みです。

値渡しでは元の変数を直接変更できない

ポインタ渡しを理解するには、値渡しとの違いを知ることが大切です。

値渡しでは、関数に渡されるのは元の変数のコピーです。

たとえば、main関数にあるpowerの値が100だったとしても、関数に渡されるのは100という値のコピーです。
関数の中でそのコピーを200に変更しても、main関数にあるpowerは100のままです。

ドラゴンボール風にたとえるなら、戦士本人ではなく、戦闘力を書き写した紙だけを修行装置に渡しているようなものです。
紙の数字を書き換えても、本物の戦士の戦闘力は変わりません。

一方、ポインタ渡しでは、戦士本人がいる部屋の住所を渡します。
修行装置はその住所をたどって、本物の戦闘力を変更できます。

渡し方関数へ渡るもの元の変数への影響
値渡し値のコピー影響しない
ポインタ渡し変数のアドレス影響する

この違いが、ポインタ渡しの大きなポイントです。

図:ポインタ渡しでは関数が元の変数へアクセスできる

この図が示していること

この図では、main関数にある2つの変数のアドレスが、swapPower関数へ渡される流れを表しています。

関数側では、int* num1 と int* num2 がアドレスを受け取ります。
そのため、関数の中で *num1 や *num2 を使うと、main関数にある元の変数へアクセスできます。

ポインタ渡しでは、関数に値のコピーではなく住所を渡すため、呼び出し元の変数を変更できます。

ポインタ渡しで値を入れ替えるプログラム

それでは、ポインタ渡しを使って、2つの変数の値を入れ替えるプログラムを見てみましょう。

ここでは、2人の戦士の戦闘力を入れ替えるイメージで考えます。

プロジェクト/ファイル名: Chap2_14/main.cpp

#include <iostream>

using namespace std;

// 2つの戦闘力を入れ替える関数のプロトタイプ宣言
void swapPower(int*, int*);

int main(int argc, char** argv) {
    // 2人の戦士の戦闘力を表す変数
    int powerA = 10;
    int powerB = 30;

    // 入れ替える前の値を表示する
    cout << "powerA = " << powerA << " powerB = " << powerB << endl;

    // 2つの変数のアドレスを関数に渡す
    swapPower(&powerA, &powerB);

    // 入れ替えた後の値を表示する
    cout << "powerA = " << powerA << " powerB = " << powerB << endl;

    // プログラムが正常に終わったことを示す
    return 0;
}

// ポインタ渡しで2つの値を入れ替える
void swapPower(int* num1, int* num2) {
    // num1が指している値を一時的に保存する
    int temp = *num1;

    // num2が指している値をnum1が指す場所へ入れる
    *num1 = *num2;

    // 保存しておいた値をnum2が指す場所へ入れる
    *num2 = temp;
}

実行結果は次のようになります。

powerA = 10 powerB = 30
powerA = 30 powerB = 10

powerAとpowerBの値が入れ替わっていることが分かります。

この処理ができるのは、swapPower関数に値そのものではなく、powerAとpowerBのアドレスを渡しているからです。

swapPower関数の引数を見てみよう

関数のプロトタイプ宣言は、次のようになっています。

void swapPower(int*, int*);

これは、swapPower関数がint型のポインタを2つ受け取るという意味です。

関数の定義を見ると、次のようになっています。

void swapPower(int* num1, int* num2)

num1とnum2は、どちらもint型のポインタです。
つまり、int型変数のアドレスを受け取るための変数です。

main関数では、次のように呼び出しています。

swapPower(&powerA, &powerB);

ここで &powerA は powerA のアドレスです。
&powerB は powerB のアドレスです。

main関数側swapPower関数側意味
&powerAnum1powerAのアドレスを受け取る
&powerBnum2powerBのアドレスを受け取る
powerA*num1num1が指す先の値
powerB*num2num2が指す先の値

つまり、swapPower関数の中で *num1 と書くと、powerAの値を扱っていることになります。
*num2 と書くと、powerBの値を扱っていることになります。

なぜ一時変数tempが必要なのか

値を入れ替えるときには、一時的に値を保存する変数が必要です。
今回のプログラムでは、tempを使っています。

int temp = *num1;

これは、num1が指している先の値をtempに保存しています。

なぜ必要なのでしょうか。

たとえば、powerAが10、powerBが30だとします。

いきなり

*num1 = *num2;

とすると、powerAの値は30になります。
しかし、この時点でpowerAに入っていた10は失われてしまいます。

そのため、最初にtempへ10を避難させておきます。

ドラゴンボール風にたとえるなら、2人の戦士の戦闘力を入れ替える前に、片方の戦闘力を一時保管カプセルに退避させるようなものです。

変数役割
*num11人目の戦闘力
*num22人目の戦闘力
temp一時保管用のカプセル

tempがあることで、元の値を失わずに安全に入れ替えできます。

swapPower関数の処理を順番に見る

swapPower関数の中では、次の3段階で値を入れ替えています。

int temp = *num1;
*num1 = *num2;
*num2 = temp;

powerAが10、powerBが30だった場合、流れは次のようになります。

手順*num1*num2temp処理の内容
開始時1030未設定powerAが10、powerBが30
1103010tempに*num1を保存
2303010num1にnum2を代入
3301010*num2にtempを代入

最終的に、*num1は30、*num2は10になります。

num1はpowerAを指しているので、powerAは30になります。
num2はpowerBを指しているので、powerBは10になります。

つまり、関数の中でポインタを使って値を入れ替えることで、main関数側の変数そのものが変わります。

図:tempを使って2つの値を安全に入れ替える

この図が示していること

この図では、swapPower関数の中で、tempを使って2つの値を入れ替える流れを表しています。

最初に *num1 の値をtempへ保存します。
次に *num1 へ *num2 の値を入れます。
最後に *num2 へtempに保存しておいた値を入れます。

この手順によって、片方の値を失わずに、安全に2つの値を入れ替えることができます。

ポインタ渡しなら呼び出し元の変数を変更できる

今回のプログラムでいちばん大切なのは、swapPower関数がmain関数の変数を変更していることです。

main関数には、powerAとpowerBがあります。

int powerA = 10;
int powerB = 30;

swapPower関数には、次のようにアドレスを渡しています。

swapPower(&powerA, &powerB);

関数側では、num1とnum2がそのアドレスを受け取ります。

void swapPower(int* num1, int* num2)

そして、num1やnum2を使って、指している先の値を変更しています。

このため、swapPower関数が終わったあと、main関数側のpowerAとpowerBの値も変わっています。

関数の中の表現実際に操作しているもの
*num1powerA
*num2powerB
*num1 = *num2powerAにpowerBの値を代入
*num2 = temppowerBに保存しておいた値を代入

このように、ポインタ渡しを使うと、関数から呼び出し元の変数を直接変更できます。

ポインタ渡しは複数の値を変更したいときに役立つ

通常、関数は戻り値を1つだけ返します。

たとえば、int型の戻り値なら1つの整数を返せます。
double型の戻り値なら1つの小数値を返せます。

しかし、関数の中で複数の値を変更したい場面もあります。

今回のswapPower関数では、powerAとpowerBの2つの値を変更しています。
戻り値はvoidですが、ポインタ渡しによって呼び出し元の2つの変数を変更できています。

方法できること
戻り値基本的には1つの結果を返す
ポインタ渡し複数の呼び出し元変数を変更できる

ドラゴンボール風にたとえるなら、戻り値は修行結果を1枚の報告書として返す方法です。
ポインタ渡しは、修行場にある複数の戦闘力カプセルを直接書き換える方法です。

このため、ポインタ渡しは、複数の値を関数内で変更したいときに便利です。

値渡しとポインタ渡しの違いを整理する

値渡しとポインタ渡しの違いを、ここで整理しておきましょう。

比較項目値渡しポインタ渡し
関数に渡すもの値のコピー変数のアドレス
関数内で元の変数を変更できないできる
書き方func(a);func(&a);
関数側の引数int numint* num
関数内での扱いnum*num
イメージ戦闘力のコピーを渡す戦士のいる部屋の住所を渡す

値渡しは安全で分かりやすいです。
関数の中で値を変更しても、呼び出し元に影響しません。

ポインタ渡しは強力です。
関数の中から呼び出し元の変数を変更できます。

ただし、強力なぶん、どの変数を変更しているのかをしっかり意識する必要があります。

ポインタ渡しで注意したいこと

ポインタ渡しを使うときは、次の点に注意しましょう。

型を合わせる

int型の変数のアドレスを渡すなら、関数側もint*で受け取ります。

void swapPower(int* num1, int* num2);

double型の変数を扱うなら、double*で受け取る必要があります。

アドレスを渡すときは&を付ける

通常の変数をポインタ引数に渡すときは、&を付けてアドレスを渡します。

swapPower(&powerA, &powerB);

関数内で値を扱うときは*を付ける

ポインタが指している先の値を扱うときは、*を付けます。

*num1 = *num2;

NULLや不正なアドレスに注意する

ポインタが有効な変数を指していない状態で *p のように使うと危険です。
ポインタを使う前に、正しいアドレスが入っているかを意識することが大切です。

ポインタ渡しで覚えておきたい記号

ポインタ渡しでは、& と * が何度も出てきます。
ここを整理しておくと、コードが読みやすくなります。

記号使う場所意味
&関数を呼び出す側変数のアドレスを取り出す
*ポインタ変数の宣言ポインタであることを示す
*関数の中で値を扱うとき指している先の値を表す

同じ * でも、使う場所によって意味の見え方が変わります。

int* num1;

これは、num1がint型のポインタであることを示します。

*num1 = 30;

これは、num1が指している先の値に30を代入するという意味です。

ドラゴンボール風に覚えるなら、&は住所を取り出すスカウター操作、*は住所の先にある本体へアクセスする操作です。

ポインタ渡しの流れを順番に確認する

今回のプログラムの流れを、もう一度順番に整理します。

順番処理内容
1powerAとpowerBを用意する10と30を入れる
2値を表示する入れ替え前を確認する
3swapPower(&powerA, &powerB);を呼び出す2つの変数のアドレスを渡す
4num1とnum2がアドレスを受け取る関数内で元の変数にアクセスできる
5tempに*num1を保存する片方の値を一時退避する
6num1にnum2を代入するpowerAの値が変わる
7*num2にtempを代入するpowerBの値が変わる
8main関数に戻る入れ替え後の値を表示する

この流れを理解できると、ポインタ渡しの基本はかなりつかめています。

大切なのは、関数の中だけで値が変わっているのではなく、main関数側の変数そのものが変わっているという点です。

ポインタ渡しで身につけたい感覚

ポインタ渡しは、C++の中でもとても重要な考え方です。

最初は、&や*が多くて難しく感じるかもしれません。
でも、意味を分けて考えると理解しやすくなります。

&は、変数の住所を取り出します。
int* は、住所を受け取るためのポインタ変数です。
*p は、その住所の先にある値です。

この3つの関係を、住所のイメージでつなげて覚えると、ポインタ渡しはぐっと分かりやすくなります。

覚えること内容
&変数名変数のアドレスを渡す
型* ポインタ名アドレスを受け取る
*ポインタ名アドレスの先の値を扱う
ポインタ渡し関数から呼び出し元の変数を変更できる
temp値を入れ替えるときの一時保管場所

ドラゴンボール風に言えば、ポインタ渡しは、関数という修行装置に戦士本人の居場所を教える技です。
居場所が分かるからこそ、修行装置は本物の戦闘力カプセルを書き換えられます。

値のコピーではなく、元の変数に直接届く。
これがポインタ渡しの基本です。