C言語のきほん|多重ループの終了方法

内側だけ抜けるのか、外側まで終わるのか。多重ループの終了方法を知ると、処理の流れがすっきり見えてきます

ループ処理に慣れてくると、次に出会いやすいのが多重ループです。
たとえば、行と列を組み合わせて表を調べたり、2つの条件を順番に確認したりするときには、for文の中にfor文を書く場面がよくあります。

ただ、多重ループでは どこまで終了するのか を正しく理解しておくことがとても大切です。
特に break文 を使ったとき、内側のループだけが終わるのか、それとも外側のループまで終わるのかが分かっていないと、思った通りに動かないプログラムになってしまいます。

多重ループでは、break文はとても便利ですが、単純な1重のループとは少し感覚が違います。
内側のループからは抜けられても、外側のループはそのまま続く、という動きをしっかり押さえておく必要があります。

ここでは、多重ループの中で break文 がどのように動くのかを最初に確認し、そのあとで 外側のループまで終了したいときの方法 を見ていきます。
フラグ変数を使う方法と goto文を使う方法の違いも含めて、やさしく丁寧に整理していきます。

多重ループでのbreak文の基本

多重ループの中で break文 を使った場合、終了するのは 現在実行している一番内側のループだけ です。
ここがまず最重要ポイントです。

たとえば、for文の中にfor文がある場合、内側のfor文の中で break を実行しても、外側のfor文は終わりません。
内側のループを抜けたあと、外側のループの次の処理へ進み、必要なら次の繰り返しも行います。

この動きをひとことで言うと、break文は 今いるループだけを終了する 文です。

まずはイメージでつかもう

多重ループの break文 の動きを、流れで表すと次のようになります。

つまり、内側で break が実行されても、外側のループ自体はまだ続いている、ということです。

サンプルプログラムで確認しよう

3行3列の座席を順番に確認していき、2行目2列目に「確認をやめる位置」が来たら、まずは内側のループだけを終了するプログラムです。

サンプルプログラム

ファイル名:9_11_1.c

#include <stdio.h>

int main(void)
{
    int row, col;

    for (row = 1; row <= 3; row++) {
        for (col = 1; col <= 3; col++) {
            printf("%d行%d列の座席を確認中です。\n", row, col);

            if (row == 2 && col == 2)
                break;    // 抜けるのは内側のループだけ
        }

        printf("%d行目の確認が終わりました。\n", row);
    }

    printf("すべての確認処理が終わりました。\n");

    return 0;
}

実行結果の例

1行1列の座席を確認中です。
1行2列の座席を確認中です。
1行3列の座席を確認中です。
1行目の確認が終わりました。
2行1列の座席を確認中です。
2行2列の座席を確認中です。
2行目の確認が終わりました。
3行1列の座席を確認中です。
3行2列の座席を確認中です。
3行3列の座席を確認中です。
3行目の確認が終わりました。
すべての確認処理が終わりました。

この結果から分かること

注目したいのは、2行2列で break が実行されたあとも、3行目の処理が続いていることです。

つまり、

  • 2行2列で内側のループは終了した
  • でも外側のループは終了していない
  • そのため row が 3 になって処理が続いた

という流れになっています。

この動きは、多重ループの break文 を学ぶうえでとても大切です。
多くの人が最初につまずきやすいのは、breakを使えば全部終わると思ってしまうところです。
でも実際には、breakで抜けられるのはその場にいる一番内側のループだけです。

どこで止まって、どこが続くのか

このプログラムでは、次の部分が終了のきっかけになっています。

if (row == 2 && col == 2)
    break;

この条件が真になると、内側のfor文はそこで終了します。
そのため、2行目では 2行3列 の表示は行われません。

ただし、内側のループを抜けたあと、外側のループに書かれているこの文は実行されます。

printf("%d行目の確認が終わりました。\n", row);

そしてそのあと、外側のfor文は次の繰り返しに進みます。
だから 3行目の確認も始まるわけです。

多重ループのbreak文を表で整理

ここで動きを表にして整理しておきましょう。

場面break文の動き
1重ループでbreakそのループを終了する
2重ループの内側でbreak内側のループだけ終了する
2重ループの外側でbreak外側のループを終了する
3重以上のループでbreak実行中の一番内側のループだけ終了する

つまり、break文は 何重ループか ではなく、今どのループの中にいるか で動きが決まります。

外側のループまで終了したいときはどうするか

多重ループでは、ある条件が見つかったら 外側のループまでまとめて終わらせたい ことがあります。

たとえば次のような場面です。

外側まで終了したい理由
表の中から目的の値を探す見つかったらもう探索を続ける必要がない
二次元データを調べる異常値を見つけたら処理全体を止めたい
組み合わせを試す条件に合う組み合わせが見つかったら終了したい

このようなとき、内側だけ break しても、外側が続いてしまうので不十分です。
そこで、外側のループまで終了するための工夫が必要になります。

代表的な方法は次の2つです。

  • フラグ変数を使う
  • goto文を使う

フラグ変数を使って外側のループも終了する

まずは、もっとも基本的で分かりやすい方法である フラグ変数 を使う方法です。

フラグ変数とは、何か特別な出来事が起きたことを記録しておくための変数です。
たとえば、終了したい条件が成立したら end_flag に 1 を入れておき、内側を break したあとで外側でもその値を確認します。

フラグ変数を使った例

ここでは、3行3列の棚を調べていて、2行2列で探し物が見つかったら確認処理を全体ごと終える例に変えてみます。

ファイル名:9_11_2.c

#include <stdio.h>

int main(void)
{
    int row, col;
    int found = 0;    // 見つかったかどうかを記録するフラグ

    for (row = 1; row <= 3; row++) {
        for (col = 1; col <= 3; col++) {
            printf("%d段%d番を確認しています。\n", row, col);

            if (row == 2 && col == 2) {
                printf("探していた場所を見つけました。\n");
                found = 1;    // フラグを立てる
                break;        // まずは内側のループを終了する
            }
        }

        if (found == 1)
            break;            // 外側のループも終了する

        printf("%d段目の確認が終わりました。\n", row);
    }

    printf("確認処理を終了します。\n");

    return 0;
}

実行結果の例

1段1番を確認しています。
1段2番を確認しています。
1段3番を確認しています。
1段目の確認が終わりました。
2段1番を確認しています。
2段2番を確認しています。
探していた場所を見つけました。
確認処理を終了します。

フラグ変数の方法を順番に理解する

この方法では、処理の流れが次のようになります。

  1. 外側のループが始まる
  2. 内側のループで順番に調べる
  3. 条件に一致したら found に 1 を入れる
  4. 内側のループを break で終了する
  5. 外側に戻ったら found を確認する
  6. found が 1 なら外側のループも break で終了する

流れを図で表すと、次のようになります。

この方法の良いところは、処理の意味が分かりやすいことです。
見つかったから終了する、という意図がコードに素直に表れます。

フラグ変数を使う方法の長所と注意点

観点内容
長所処理の流れが読みやすい
長所何が起きたかを変数で明示できる
長所goto文を使わずに書ける
注意点ループが深くなるとフラグ確認が増える
注意点フラグ名が分かりにくいと意図が伝わりにくい

特に初心者のうちは、まずこのフラグ変数の方法をしっかり身につけるのがおすすめです。

goto文を使って一気に抜ける方法

もうひとつの方法が goto文 です。
goto文は、指定したラベルへ処理をジャンプさせる制御文です。

多重ループの中で条件が成立したとき、ループの外にあるラベルへ一気に飛ぶことで、内側も外側もまとめて抜けることができます。

goto文を使った例

同じく棚の確認処理を例にして、今度は goto文 で一気に終了する形にしてみます。

ファイル名:9_11_3.c

#include <stdio.h>

int main(void)
{
    int row, col;

    for (row = 1; row <= 3; row++) {
        for (col = 1; col <= 3; col++) {
            printf("%d段%d番を確認しています。\n", row, col);

            if (row == 2 && col == 2) {
                printf("探していた場所を見つけました。\n");
                goto END_CHECK;    // ループの外へ一気に移動する
            }
        }

        printf("%d段目の確認が終わりました。\n", row);
    }

END_CHECK:
    printf("確認処理を終了します。\n");

    return 0;
}

goto文の動きをイメージする

goto文では、次のような流れになります。

この方法では、内側のループを抜けて、外側を確認して、さらにまた break する、という段階を踏みません。
ラベルの位置まで一気に飛ぶので、確かに強力です。

goto文の長所と注意点

観点内容
長所深い多重ループから一気に抜けやすい
長所フラグ確認を何度も書かなくてよい
注意点処理の流れを追いにくくなりやすい
注意点多用すると可読性が下がる
注意点どこへ飛ぶのか分かりにくいコードになりやすい

goto文は便利そうに見えますが、何でもかんでも使うと、読み手がコードを追いかけにくくなります。
そのため、一般には 多用しない ことが大切です。

なぜgoto文は注意が必要なのか

goto文が難しいのは、上から下へ順番に読むというプログラムの自然な流れを飛び越えてしまうからです。
条件によって急に別の場所へ移動するので、慣れないうちは処理の流れを頭の中で追いにくくなります。

特に、複数の goto と複数のラベルが入り混じると、コード全体が迷路のようになりやすいです。
そのため、goto文は自由に使ってよい便利機能というより、必要な場面に限って慎重に使うものと考えるのがよいです。

多重ループではどちらを使えばよいか

基本的には、まず フラグ変数を使う方法 を考えるのがおすすめです。
理由は、読みやすく、意図も伝わりやすいからです。

ただし、ループが何重にもなっていて、フラグの確認が何段階にも増えてしまう場合は、goto文のほうがかえって分かりやすいケースもあります。
このあたりは、コードの見通しのよさで判断するとよいです。

比較すると、次のようになります。

方法特徴向いている場面
フラグ変数読みやすく基本的2重ループ程度、学習段階、一般的な処理
goto文一気に抜けられる深い多重ループ、終了位置が明確な場合

学習のときに特に意識したいこと

多重ループの終了方法を学ぶときは、次の3つを意識すると理解しやすくなります。

ポイント内容
breakはどこまで抜けるか一番内側のループだけを終了する
外側を止めたいならどうするかフラグ変数やgoto文を使う
読みやすさを保てているか終了条件と処理の流れが見やすいか確認する

特に break文 については、全部のループが終わるわけではない、という点をしっかり押さえておくことが大切です。

間違えやすいポイント

学習中によくある勘違いも整理しておきましょう。

よくある勘違い実際の動き
breakを使えば多重ループ全部が終わる終わるのは一番内側のループだけ
内側でbreakしたら外側の残りも実行されない外側の処理はそのまま続くことがある
goto文はいつでも便利多用すると読みにくくなりやすい
フラグ変数は面倒だから不要外側まで終了したいときに分かりやすく書ける

このあたりを意識してコードを読むと、動作の理解がかなり深まります。

もう一歩理解を深める見方

多重ループでは、ループごとに役割が違うことが多いです。
たとえば、外側は行を進める役割、内側は列を進める役割を持っているかもしれません。

そのとき break文 が実行されると、列方向の確認だけをやめるのか、行方向の確認そのものをやめたいのかを考える必要があります。
つまり、どの範囲を終わらせたいのか を意識して書くことが大切です。

この視点を持つと、breakを置く位置や、フラグ変数を使うべきかどうかが判断しやすくなります。

読みやすいコードにするためのコツ

最後に、実際に多重ループを書くときのコツを挙げておきます。

コツ内容
条件を明確にする何が起きたら終了するのかをはっきり書く
コメントを添えるなぜ抜けるのかが分かるようにする
フラグ名を分かりやすくするfound や end_flag など意味が伝わる名前にする
goto文は限定的に使う終了位置が明確な場合だけにする

多重ループは少し複雑に見えますが、内側だけ終わるのか、外側まで終わるのかを丁寧に考えると、動きがとても分かりやすくなります。
この考え方を身につけておくと、表の探索や二次元データの処理など、実践的なプログラムがずっと書きやすくなります。