C言語基礎|ポインタによる2値のソート

ポインタを“たらい回し”できれば勝ち!2値ソートで、関数とポインタの連携がスッと分かる。

2値の交換(swap)が作れるようになると、次にすぐ作れるのが 2値のソートです。
「小さいほうを前に、大きいほうを後ろに並べる」…やることは単純なんですが、ここにはポインタ学習の大事なポイントが詰まっています。

特に大事なのはこの2つです。

  • 関数 sort2 は、呼び出し元の変数を変えたいので 住所(ポインタ) を受け取る。
  • そのポインタを swap に渡すときは &を付けない(すでに住所だから)

この「&を付ける場面/付けない場面」をここでハッキリさせると、ポインタと関数の理解がかなり安定しますよ。

2値ソートとは(目的はこれだけ)

2値ソートは、「2つの値を昇順に並べる」ことです。

  • n1 <= n2 なら、そのまま(すでにソート済み)
  • n1 > n2 なら、入れ替える(swap)

2値ソートの判断

状態条件やること
すでに昇順*n1 <= *n2何もしない
逆順*n1 > *n2swap(n1, n2)

ここで注目ポイントは、比較しているのが n1 や n2 ではなく *n1 と *n2(指す先の値)というところです。

サンプルプログラム

赤チームの点数 red と 青チームの点数 blue を昇順にそろえるプログラム例です。

プロジェクト名:chap10-7-1 ソースファイル名:chap10-7-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;
}

// *a <= *b となるように並べ替える(2値ソート)
void sort2_int(int *a, int *b)
{
    if (*a > *b) {
        swap_int(a, b);   // ここは & を付けない
    }
}

int main(void)
{
    int red, blue;

    puts("2つの点数を入力してください。");
    printf("red  = ");
    scanf("%d", &red);
    printf("blue = ");
    scanf("%d", &blue);

    sort2_int(&red, &blue);  // ここは & が必要

    puts("小さい順に並べました。");
    printf("red  = %d\n", red);
    printf("blue = %d\n", blue);

    return 0;
}

ここが山場:& が必要な場所/不要な場所

この章のいちばん大事なところなので、表で一発整理します。

& を付けるかどうかの判断

場面渡したいもの書き方なぜ?
main → sort2_intred の住所、blue の住所sort2_int(&red, &blue)sort2_int は住所が欲しい
sort2_int → swap_intすでに持っている住所swap_int(a, b)a と b はもう住所(ポインタ)
もし swap_int(&a, &b) と書いたらa自身の住所、b自身の住所誤り交換対象が red/blue ではなく a/b(ポインタ変数)になる

ポインタの“たらい回し”とは(意味とメリット)

sort2_int の引数 a, b は、呼び出し元の変数の住所をコピーしたものです。
つまり sort2_int の中で a と b はこういう値を持っています。

  • a の中身:red のアドレス
  • b の中身:blue のアドレス

この「住所(アドレス)という情報」を swap_int にそのまま渡すのが、いわゆる たらい回しです。

図:住所が関数をまたいで受け渡される


sort2_int側:
a : [ &red ]     b : [ &blue ]
if (*a > *b) なら
swap_int(a,b) を呼ぶ(住所をそのまま渡す)

swap_int側:
px: [ &red ]  py: [ &blue ]
*px と *py を交換 → red と blue の中身が交換される

図の説明(読み方)

  • 箱は変数(オブジェクト)
  • a, b, px, py はポインタなので「住所」を入れる箱
  • *a や *px は「住所の先の本人(値)」を表す。
  • 住所を渡し続けることで、最終的に呼び出し元の値が更新される。

sort2_int の動作をステップで見る(*a と *b)

2値ソートは条件分岐が1つだけなので、流れはとてもシンプルです。

sort2_int の処理手順

手順判定/処理意味
1*a と *b を比較指す先の値を比べる
2*a > *b なら swap_int(a, b)逆順なら入れ替える
3それ以外は何もしないすでに昇順なので完了

よくあるミス:swap_int(&a, &b) と書いてしまう

これ、ポインタ初心者が高確率で踏むポイントです。

a と b は「red/blue の住所」を持つポインタ変数です。
&a を取ると「a 自身の住所」になってしまいます。つまり交換する対象が変わっちゃいます。

何の住所を渡している?

指しているもの
ared の住所
&redred の住所(mainで必要)
&aa というポインタ変数の住所(別物)

登場する命令(関数)と書式・何をする?

sort2_int(自作関数)

  • 書式:sort2_int(int *a, int *b)
  • 何をする?:a と b が指す先の2つの int を昇順に並べる
  • 中身:必要なら swap_int(a, b) を呼ぶ

swap_int(自作関数)

  • 書式:swap_int(int *px, int *py)
  • 何をする?:px と py が指す先の値を交換する
  • 交換対象は px/py そのものではなく *px と *py

puts

  • 書式:puts(文字列);
  • 何をする?:文字列を表示して改行も付ける

printf

  • 書式:printf(書式文字列, 引数1, 引数2, ...);
  • 何をする?:書式に従って表示する

scanf

  • 書式:scanf(書式文字列, 変数のアドレス, ...);
  • 何をする?:入力を読み取り、指定した変数へ格納する
  • ポイント:red に入力させるには &red のように住所が必要

使った表や図の説明(まとめ)

  • &判断表は「今渡しているのは値か住所か」を見分けるための整理です。
  • たらい回し図は「住所が関数間を移動しても、指す先は同じ」ことを見える化しています。
  • 手順表は sort2_int が「比較して必要なら交換」だけだと分かるように簡略化しています。
  • ミス表は &a を取ると「交換対象がズレる」落とし穴を防ぐための注意です。

演習問題

3値ソートは条件が増えて良い練習になります。ここでは「昇順に並べる」だけに集中します。

演習10-3:3つの値を昇順に並べる関数 sort3_int を作成せよ

void sort3_int(int *a, int *b, int *c);

解答例(swap_int と sort2_int を活用してOK)

プロジェクト名:chap10-7-1 ソースファイル名:chap10-7-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

void swap_int(int *px, int *py)
{
    int temp = *px;
    *px = *py;
    *py = temp;
}

void sort2_int(int *a, int *b)
{
    if (*a > *b) swap_int(a, b);
}

void sort3_int(int *a, int *b, int *c)
{
    // まず a<=b、次に b<=c を作り、
    // もう一度 a<=b を確認すると昇順になる
    sort2_int(a, b);
    sort2_int(b, c);
    sort2_int(a, b);
}

int main(void)
{
    int x, y, z;

    puts("3つの整数を入力してください。");
    printf("x = ");
    scanf("%d", &x);
    printf("y = ");
    scanf("%d", &y);
    printf("z = ");
    scanf("%d", &z);

    sort3_int(&x, &y, &z);

    puts("昇順に並べました。");
    printf("%d %d %d\n", x, y, z);

    return 0;
}

解説(なぜこれで昇順になる?)

  • sort2_int(a,b) で a<=b を保証
  • sort2_int(b,c) で b<=c を保証
  • ただし b を動かした影響で a と b が逆転する可能性があるので、最後にもう一度 sort2_int(a,b)
  • 小さな部品(2値ソート)を組み合わせて3値ソートにできるのも、関数とポインタの良さです。