トラック6・🔀制御構造

ループのネストと九九表

読む目安 約12分・ゴール:二重ループの動きを説明でき、九九表のような表形式の出力を自分で組み立てられる

アプリでこのトピックを学ぶ(無料・登録不要)

このトピックの要点

アナログ時計を見てください。長針が1周する間に、短針は1目盛りだけ進みます。外側が1回進む間に、内側は最初から最後まで回りきる。これがループのネスト(入れ子)です。

外側3回、内側3回なら、本体が実行されるのは 3×3 = 9回。掛け算になるのがポイントです。

定番中の定番、九九表を作りましょう。

#include <stdio.h>

int main(void) {
    for (int i = 1; i <= 9; i++) {          // 段
        for (int j = 1; j <= 9; j++) {      // 掛ける数
            printf("%3d", i * j);
        }
        printf("\n");                        // 1段ぶん終わったので改行
    }
    return 0;
}

9行9列の表が出ます。%3d は「3桁ぶんの幅で右寄せ」というprintfの書式指定で、これがあるおかげで桁が揃います。

このコードで本当に大事なのは、改行の位置です。printf("\n") は内側のループの外、外側のループの中にあります。ここを間違えると形が崩れます。

改行を内側のループの中に入れると、数字が1つ出るたびに改行されて縦1列になります。改行を外側のループの外に出すと、81個の数字が全部1行に並びます。「内側が回りきったら、1行ぶん終わり」——この対応を意識すると、表の出力で迷わなくなります。

ビット外側1回ぶんが「1段」。内側が回りきったら改行、って唱えると九九表は一発だよ

内側の条件に外側の変数を使うと、段ごとに長さを変えられます。

#include <stdio.h>

int main(void) {
    for (int i = 1; i <= 5; i++) {
        for (int j = 1; j <= i; j++) {   // 上限が外側の i
            printf("*");
        }
        printf("\n");
    }
    return 0;
}

* が1個、2個、3個…と増えて三角形になります。ネストの本当の威力はここで、内側と外側を連動させると一気に表現力が上がります。

前のトピックで触れた「breakは内側しか抜けない」問題も片付けておきましょう。外側まで抜けたいときはboolのフラグを使います。

#include <stdio.h>

int main(void) {
    bool found = false;

    for (int i = 1; i <= 3 && !found; i++) {   // フラグを外側の条件にも入れる
        for (int j = 1; j <= 3; j++) {
            if (i * j == 4) {
                printf("見つけた: %d x %d\n", i, j);
                found = true;
                break;                          // 内側を抜ける
            }
        }
    }
    return 0;
}

内側で found = true にしてから break すると、外側の継続条件 !found が偽になって外側も終わります。少し回りくどく見えますが、流れが1本道なので後から読む人に優しい書き方です。

罠を2つ。

その1、カウンタ名の使い回し。 外側も内側も i にすると、内側が外側のカウンタを壊して動きがめちゃくちゃになります。i, j, k と分けてください。

その2、実行回数は掛け算。 外側 n 回・内側 n 回なら本体は n×n 回です。n が 1000 なら100万回。ネストは静かに重くなります。三重にすれば n×n×n。「入れ子を1つ増やすと桁が変わる」という感覚は、今のうちに持っておいて損はありません。

まとめます。外側1周につき内側は回りきる。表の形を決めるのは改行の位置。カウンタ名は分ける。実行回数は掛け算で効いてくる。

アプリでこのトピックを学ぶ(無料・登録不要)

確認問題

確認問題 1

二重ループの中で break が働きます。外側の i ごとに count がどう増えるかを追いかけて、空欄を埋めてください。

#include <stdio.h>

int main(void) {
    int count = 0;
    for (int i = 1; i <= 3; i++) {
        for (int j = 1; j <= 3; j++) {
            if (i * j > 4) {
                break;
            }
            count++;
        }
    }
    printf("%d\n", count);
    return 0;
}
icountが増えた回数その段おわりのcount
133
2??
3??
答えを見る

空欄の答え(上から順に):2、5、1、6

i * j が 4 を超えた時点で内側を break します。i=1 のときは 1・2・3 と全部4以下なので3回ふえて count は 3。i=2 のときは j=1(積2)と j=2(積4)が4以下、j=3(積6)で break なので2回ふえて count は 5。i=3 のときは j=1(積3)だけが4以下、j=2(積6)で break なので1回ふえて count は 6。break が抜けるのは内側のループだけなので、外側は3周きちんと回りきります。最終的な出力は 6 です。

確認問題 2

九九表(9行9列)を表示するプログラムです。行を正しい順に並べ替えてください。

            printf("%3d", i * j);
        for (int j = 1; j <= 9; j++) {
        printf("\n");
        }
    for (int i = 1; i <= 9; i++) {
    return 0;
    }
#include <stdio.h>
int main(void) {
}
答えを見る

正しい順番:

#include <stdio.h>
int main(void) {
    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <= 9; j++) {
            printf("%3d", i * j);
        }
        printf("\n");
    }
    return 0;
}

外側の for が「段」、内側の for が「掛ける数」です。内側が9回まわりきったところで改行を1回入れると、1段ぶんがちょうど1行になります。改行を内側のループの中に入れると数字が1つ出るたびに改行されて縦1列になり、外側のループの外に出すと81個の数字が全部1行に並びます。改行の位置が表の形を決める、というのが二重ループで表を作るときの勘所です。%3d は3桁ぶんの幅で右寄せする書式で、これがあるおかげで桁が揃います。

「制御構造」の目次

  1. 流れを変える——制御構造の全体像
  2. if文——もしも、の分かれ道
  3. else if / else の分かれ道設計
  4. 条件式の罠——= と == の悲劇
  5. 論理演算子と短絡評価
  6. switch文とfallthrough
  7. for文——回数が決まった繰り返し
  8. while文——条件が決める繰り返し
  9. do-while文と入力チェック
  10. breakとcontinueの使いどころ
  11. ループのネストと九九表
  12. 無限ループとの付き合い方

もっと先へ:ポインタ・メモリ・ファイル入出力・セキュアコーディングを含む全26トラックと、段位検定・模試のフルセットは完全版に収録しています。

完全版の販売ページは準備中です。

ほかのトラック

📚 姉妹教材:手を動かして覚える Linux 教材 — Linuxとインフラの仕組みを地図で学ぶ

Web版