C言語の基本|多重ループの使い方

外側で行を進め、内側で列を埋める。多重ループで、表や座標のような2次元の処理をすっきり作ろう。

1つのループで表現できるのは、基本的に一方向の繰り返しです。しかし、プログラムでは、行と列のように2つの方向を組み合わせて処理したい場面があります。

たとえば、次のような処理です。

  • 同じ並びを複数行表示する
  • 建物の各階にある設備を順番に確認する
  • 行と列で構成された表を作る
  • 記号を並べて図形を描く
  • マス目の座標を順番に表示する

このような処理で役立つのが多重ループです。

多重ループは、あるループの中に別のループを入れる書き方です。特に、ループが外側と内側の2段になっているものを二重ループと呼びます。

二重ループでは、外側と内側に次のような役割を持たせるのが基本です。

  • 外側のループは縦方向となる行を担当する
  • 内側のループは横方向となる列を担当する
  • 内側のループが終わったら、外側のループを次へ進める

この役割分担を理解すると、表やマス目を作る処理を読みやすく記述できます。

図1:外側の1回で内側が最後まで繰り返す流れ

この図から分かること

外側のループが1階を担当している間に、内側のループは区域1から区域4までをすべて処理します。

内側のループが終了すると、外側のループが2階へ進みます。その時点で内側のループは再び区域1から始まります。

外側が1回進むごとに、内側が最初から最後まで繰り返されることが二重ループの重要な特徴です。

多重ループと二重ループの違い

多重ループは、ループを入れ子にした構造全体を表す言葉です。

呼び方ループの段数処理のイメージ
単一ループ1段一方向の繰り返し
二重ループ2段行と列を使った繰り返し
三重ループ3段層、行、列を使った繰り返し
多重ループ2段以上の総称入れ子になったループ全体

最初に理解したいのは二重ループです。

二重ループの動きが分かれば、三重ループでも考え方は変わりません。ただし、段数が増えるほど変数の組み合わせや実行回数が増えるため、処理を追いにくくなります。

for文を使った二重ループの基本形

for文の中に、もう1つのfor文を書くと二重ループになります。

for (外側の初期設定; 外側の継続条件; 外側の再設定) {
    for (内側の初期設定; 内側の継続条件; 内側の再設定) {
        繰り返す処理;
    }
}

行と列として書き換えると、次のようになります。

for (行の初期設定; 行の継続条件; 行の再設定) {
    for (列の初期設定; 列の継続条件; 列の再設定) {
        1マス分の処理;
    }
}
ループ主な役割典型的な処理
外側のループ行を進める1行目から4行目まで
内側のループ列を進める1列目から4列目まで
内側の処理1つ分を表示する数値や座標を表示する
内側の終了後1行を終える改行する

二重ループの実行順序

次の二重ループを考えてみましょう。

for (int row = 1; row <= 4; row++) {
    for (int col = 1; col <= 4; col++) {
        処理;
    }
}

実行されるrowとcolの組み合わせは、次のようになります。

外側のrow内側のcolの変化
11 → 2 → 3 → 4
21 → 2 → 3 → 4
31 → 2 → 3 → 4
41 → 2 → 3 → 4

処理順を並べると、次のとおりです。

  1. rowを1にする
  2. colを1にする
  3. colが4以下の間、内側の処理を繰り返す
  4. colが5になると内側のループを終了する
  5. rowを2にする
  6. 内側のcolを再び1にする
  7. 同じようにcolが4になるまで処理する
  8. rowが5になるまで繰り返す

外側が4回、内側も4回なので、最も内側の処理は合計16回実行されます。

外側の回数 × 内側の回数
4 × 4 = 16回

多重ループを使わずに同じ行を表示する

最初に、多重ループを使わず、同じ数字の並びを4行表示してみましょう。

それぞれの行で、区域番号1から4までを表示します。

同じ区域番号の並びを4回繰り返して書くプログラム

ファイル名:9_8_1.c

#include <stdio.h>

int main(void)
{
    int zone;

    for (zone = 1; zone <= 4; zone++) {
        printf("Z%d ", zone);
    }
    printf("\n");

    for (zone = 1; zone <= 4; zone++) {
        printf("Z%d ", zone);
    }
    printf("\n");

    for (zone = 1; zone <= 4; zone++) {
        printf("Z%d ", zone);
    }
    printf("\n");

    for (zone = 1; zone <= 4; zone++) {
        printf("Z%d ", zone);
    }
    printf("\n");

    return 0;
}
実行結果の例
Z1 Z2 Z3 Z4
Z1 Z2 Z3 Z4
Z1 Z2 Z3 Z4
Z1 Z2 Z3 Z4
プログラムの詳しい解説

各for文では、zoneを1から4まで増やしています。

for (zone = 1; zone <= 4; zone++)

ループ本体では、現在の区域番号を表示します。

printf("Z%d ", zone);

1つのfor文が終了すると、改行を表示します。

printf("\n");

この書き方でも、目的どおりの結果を表示できます。しかし、同じfor文とprintfを4回書いているため、重複が多くなっています。

行数を6行へ増やす場合は、同じ処理をさらに2つ追加しなければなりません。1行あたりの区域数を変更するときは、4つのfor文をすべて修正する必要があります。

二重ループで重複を減らす

同じ処理を二重ループで書くと、行数と列数を別々のループで管理できます。

二重ループで4階4区域の一覧を表示するプログラム

ファイル名:9_8_2.c

#include <stdio.h>

int main(void)
{
    int floor;
    int zone;

    for (floor = 1; floor <= 4; floor++) {
        for (zone = 1; zone <= 4; zone++) {
            printf("Z%d ", zone);
        }

        printf("\n");
    }

    return 0;
}
実行結果の例
Z1 Z2 Z3 Z4
Z1 Z2 Z3 Z4
Z1 Z2 Z3 Z4
Z1 Z2 Z3 Z4
プログラムの詳しい解説

外側のfor文では、建物の階を1から4まで進めます。

for (floor = 1; floor <= 4; floor++)

外側のループ本体に、区域を処理する内側のfor文があります。

for (zone = 1; zone <= 4; zone++)

floorが1の間、zoneは1から4まで変化します。そのため、最初の行にZ1からZ4までが表示されます。

内側のループが終わったら、次のprintfで改行します。

printf("\n");

この改行は、内側のfor文の外、外側のfor文の中にあります。そのため、区域を4つ表示し終わるたびに1回だけ実行されます。

その後、外側のfloorが2へ増えます。内側のfor文は初期設定から始まるため、zoneは再び1になります。

floorzoneの変化表示後の動作
11 → 2 → 3 → 4改行する
21 → 2 → 3 → 4改行する
31 → 2 → 3 → 4改行する
41 → 2 → 3 → 4改行する

改行を書く場所が重要

二重ループでは、改行を書く位置によって表示結果が変わります。

今回のプログラムでは、改行を内側のループの後に書いています。

for (floor = 1; floor <= 4; floor++) {
    for (zone = 1; zone <= 4; zone++) {
        printf("Z%d ", zone);
    }

    printf("\n");
}

この配置では、1行分となる4区域を表示した後に改行します。

改行を内側のループへ入れると、区域を1つ表示するたびに改行されます。

for (zone = 1; zone <= 4; zone++) {
    printf("Z%d\n", zone);
}

これでは横方向に並ばず、すべて縦に表示されます。

内側のループで1行の中身を作り、内側のループが終了した後で改行するという考え方が大切です。

図2:二重ループが4行4列のマス目を埋める様子

この図から分かること

外側のfloorが現在の行を決め、内側のzoneがその行の左から右までを順番に処理します。

zoneが1から4まで進むと1行が完成します。そこで改行し、floorを次へ進めます。

4行と4列を組み合わせているため、最も内側にあるprintfは合計16回実行されます。

floorとzoneで役割を明確にする

二重ループでは、外側と内側で異なる変数を使います。

変数担当する方向変化するタイミング
floor縦方向・行内側のループがすべて終わった後
zone横方向・列内側の処理を1回実行するたび
row一般的な行番号外側のループで使うことが多い
col一般的な列番号内側のループで使うことが多い

iやjを使ってもプログラムは動きます。

for (i = 1; i <= 4; i++) {
    for (j = 1; j <= 4; j++) {
        /* 処理 */
    }
}

ただし、floorやzone、rowやcolのような名前を使うと、それぞれの変数が何を担当しているか理解しやすくなります。

行番号と列番号を組み合わせる

内側の処理では、外側と内側の両方の変数を使用できます。

次のプログラムでは、階番号と区域番号を組み合わせ、各防災区域の位置を表示します。

階番号と区域番号を組み合わせて防災区域を表示するプログラム

ファイル名:9_8_3.c

#include <stdio.h>

int main(void)
{
    int floor;
    int zone;

    for (floor = 1; floor <= 4; floor++) {
        for (zone = 1; zone <= 4; zone++) {
            printf("[F%d-Z%d] ", floor, zone);
        }

        printf("\n");
    }

    return 0;
}
実行結果の例
[F1-Z1] [F1-Z2] [F1-Z3] [F1-Z4]
[F2-Z1] [F2-Z2] [F2-Z3] [F2-Z4]
[F3-Z1] [F3-Z2] [F3-Z3] [F3-Z4]
[F4-Z1] [F4-Z2] [F4-Z3] [F4-Z4]
プログラムの詳しい解説

外側のfloorは、現在処理している階を表します。内側のzoneは、その階にある区域を表します。

printf("[F%d-Z%d] ", floor, zone);

floorが1の間、zoneが1から4まで変化するため、次の4つが表示されます。

[F1-Z1] [F1-Z2] [F1-Z3] [F1-Z4]

内側のループが終わると改行し、floorが2になります。内側のzoneは再び1から始まるため、2階の区域が順番に表示されます。

このように、外側と内側の変数を組み合わせると、行と列で決まる各マスの位置を表現できます。

内側のループは毎回初期状態に戻る

for文では、内側のループへ入るたびに初期設定が実行されます。

for (zone = 1; zone <= 4; zone++)

外側のfloorが次へ進むと、内側のzoneは再び1に設定されます。

内側の変数が前の行の続きから始まるわけではありません。この再初期化によって、すべての階で区域1から区域4までを処理できます。

while文でも二重ループを作れる

多重ループはfor文だけの機能ではありません。while文の中にwhile文を書くこともできます。

基本的な形は次のとおりです。

外側の変数を初期化;

while (外側の条件) {
    内側の変数を初期化;

    while (内側の条件) {
        処理;
        内側の変数を更新;
    }

    外側の変数を更新;
}

while文で二重ループを作る場合は、内側の変数を初期化する場所が重要です。

内側の変数は、外側のループが1回進むたびに開始値へ戻す必要があります。そのため、内側の変数の初期化は外側のwhile文の中、内側のwhile文の直前に書きます。

図3:while文の二重ループで内側の変数を戻す位置

この図から分かること

while文の二重ループでは、内側の変数を自分で開始値へ戻す必要があります。

zoneを外側のループよりも前で1回だけ初期化すると、1行目の処理が終わった時点でzoneは終了値を超えています。そのまま2行目へ進んでも、内側の条件は最初から偽になり、区域を処理できません。

多重ループを書くときの確認ポイント

多重ループを作るときは、次の点を確認すると処理を追いやすくなります。

  • 外側のループが何を担当するか
  • 内側のループが何を担当するか
  • 外側が1回進む間に内側が何回動くか
  • 内側の変数が適切に初期化されるか
  • 内側と外側の変数が正しく更新されるか
  • 改行が内側のループの外にあるか
  • 最も内側の処理が合計何回実行されるか
  • ネストを深くしすぎていないか

多重ループは、外側と内側の役割をはっきり分けると理解しやすくなります。内側で1行分の内容を作り、外側で必要な行数を作るという考え方を使えば、表や座標のような2次元の処理を重複の少ないコードで表現できます。