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 が更新されません。
  • 交換と同じで「更新したい本人の住所を渡す」がポイントです。