
C言語基礎|ポインタによる2値の交換
交換の本質は「本人」を入れ替えること。ポインタを使えば関数で2つの値を確実に交換できる。
2つの値を入れ替える処理は、プログラミングの基本中の基本ですよね。
でもC言語で「関数を使って交換したい」となると、ふつうの引数(値渡し)ではうまくいきません。
理由はシンプルで、関数が受け取る引数はコピーだからです。
コピー同士を入れ替えても、呼び出し元の変数はそのまま。
そこで登場するのが ポインタ。
関数に「値」ではなく「住所」を渡すことで、関数内で呼び出し元の変数そのもの(本人)を入れ替えられるようになります。
この節では、交換処理の手順をポインタで丁寧に追いながら、
*(参照外し)と一時変数 temp の意味をしっかり腹落ちさせていきます。

まず確認:値渡しで swap を作ると失敗する
NG例(値渡しで交換しようとしても、呼び出し元は変わらない)
void swap_bad(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
図:コピーを入れ替えても、元の箱は動かない

解決:ポインタで「本人」を交換する
ポインタを引数にすると、関数はこういう依頼を受け取れます。
「この住所の箱と、この住所の箱の中身を入れ替えてください」
値渡しとポインタ渡しの違い
| 方式 | 関数が受け取るもの | 入れ替えの対象 | 呼び出し元は変わる? |
|---|---|---|---|
| 値渡し | 値のコピー | コピー同士 | 変わらない |
| ポインタ渡し | 住所(アドレス) | 住所の先の本人 | 変わる |
サンプルプログラム
left と right の2つの数を交換するプログラム例です。
プロジェクト名:chap10-6-1 ソースファイル名:chap10-6-1.c
Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。
#include <stdio.h>
// 2値の交換(pxとpyが指す先の値を交換)
void swap_int(int *px, int *py)
{
int temp = *px;
*px = *py;
*py = temp;
}
int main(void)
{
int left, right;
puts("左右の数値を入力してください。");
printf("left = ");
scanf("%d", &left);
printf("right = ");
scanf("%d", &right);
swap_int(&left, &right);
puts("入れ替え後の結果です。");
printf("left = %d\n", left);
printf("right = %d\n", right);
return 0;
}swap_int の中で何が起きている?(図でステップ解説)
ここがこの記事のメインです。1行ずつ、箱と矢印で追いかけます。
図:交換の前(pxはleft、pyはrightを指す)
main側:
left : [ 57 ] right: [ 21 ]
▲ ▲
│ │
px(=&left) py(=&right)
ステップ1:temp = *px;
temp : [ 57 ] (leftの中身を退避)
left : [ 57 ]
right: [ 21 ]
- *px は left の別名(エイリアス)
- 先に退避しないと、上書きで元の値が消えます。
ステップ2:*px = *py;
temp : [ 57 ]
left : [ 21 ] (rightの値がleftへ)
right: [ 21 ]
- *py は right の別名
- right の値が left にコピーされます。
ステップ3:*py = temp;
temp : [ 57 ]
left : [ 21 ]
right: [ 57 ] (退避していた値がrightへ)
これで入れ替え完了です。
なぜ temp が必要?(交換の鉄則)
交換は「上書き」の連続なので、退避場所がないと片方の値が消えます。
temp なしでやると何が起きる?
| やり方 | 結果 |
|---|---|
| *px = *py; *py = *px; | 両方同じ値になりやすい(元が消える) |
| temp を使う | 元の値を守りながら交換できる。 |
重要ポイント:temp はポインタではない
swap_int の temp は int 型です。
交換している対象は px や py ではなく、*px と *py(指している先の値)です。
何を交換している?
| 名前 | 役割 | 型 |
|---|---|---|
| px, py | 住所を持つ(どの箱を触るか) | int * |
| *px, *py | 本人(箱の中身) | int |
| temp | 一時退避用の箱 | int |
「実行時に交換対象を変える」発展イメージ
ポインタ引数での交換は、呼び出し側で &a と &b を選べば、交換対象を柔軟に変えられます。
この「住所を渡す」という仕組みが、関数設計の自由度を大きくします。
登場する命令(関数)と書式・何をする?
swap_int(自作関数)
- 書式:swap_int(int *px, int *py)
- 何をする?:px と py が指す先の int の値を交換する。
- ポイント:px と py は住所を受け取るので、呼び出し側は &left, &right を渡す。
puts
- 書式:puts(文字列);
- 何をする?:文字列を表示し、改行も出す。
printf
- 書式:printf(書式文字列, 引数1, 引数2, ...);
- 何をする?:書式に従って表示する。
scanf
- 書式:scanf(書式文字列, 変数のアドレス, ...);
- 何をする?:入力を読み取り、指定した変数へ格納する
- ポイント:left に入力させるには &left が必要(住所を渡して書き込ませる)
使った表や図の説明(読み方のコツ)
- 交換の図は「箱=変数」「矢印=ポインタが指す関係」で表しています。
- ステップ図は swap_int 内の3行が、どの箱をどう変えるかを順番に見える化しています。
- 表は「住所(ポインタ)」と「本人(*で参照した値)」を混同しないための整理です。
演習問題
日付の増減は条件分岐が多くて重くなりがちなので、同じ“更新系”の発想で、もう少し軽めの題材にします。
「時刻」を1分進める/戻す問題にすると、仕組みは同じで理解が進みやすいです。
演習10-2:時刻を1分進める/1分戻す関数を作成せよ
時刻 h 時 m 分 を、次の時刻または前の時刻に更新する関数を作成せよ(24時間制、0:00~23:59)。
void increment_time(int *h, int *m);
void decrement_time(int *h, int *m);
解答例
プロジェクト名:chap10-6-2 ソースファイル名:chap10-6-2.c
Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。
#include <stdio.h>
void increment_time(int *h, int *m)
{
(*m)++;
if (*m == 60) {
*m = 0;
(*h)++;
if (*h == 24) *h = 0;
}
}
void decrement_time(int *h, int *m)
{
(*m)--;
if (*m == -1) {
*m = 59;
(*h)--;
if (*h == -1) *h = 23;
}
}
int main(void)
{
int h, m;
puts("時刻を入力してください。");
printf("時 = ");
scanf("%d", &h);
printf("分 = ");
scanf("%d", &m);
increment_time(&h, &m);
printf("1分後 : %02d:%02d\n", h, m);
decrement_time(&h, &m);
decrement_time(&h, &m);
printf("1分前 : %02d:%02d\n", h, m);
return 0;
}解説(ポインタが効いているところ)
- 関数は h と m の「住所」を受け取り、*h と *m を直接更新します。
- 値渡しにしてしまうと、関数内のコピーが変わるだけで main の h,m が更新されません。
- 交換と同じで「更新したい本人の住所を渡す」がポイントです。
