多重ループ

基本的な多重ループ

ループ文の中にさらにループ文を記述にすることを多重ループといいます。

以下のコードは、九九の計算結果の一覧を表示するサンプルです。


#include <stdio.h>

int main()
{
    for (int i = 1; i < 10; i++)
    {
        printf("%dの位:", i);
        for (int j = 1; j < 10; j++)
        {
            printf(" %2d", i * j);
        }
        printf("\n");
    }
    getchar();
}
1の位:  1  2  3  4  5  6  7  8  9
2の位:  2  4  6  8 10 12 14 16 18
3の位:  3  6  9 12 15 18 21 24 27
4の位:  4  8 12 16 20 24 28 32 36
5の位:  5 10 15 20 25 30 35 40 45
6の位:  6 12 18 24 30 36 42 48 54
7の位:  7 14 21 28 35 42 49 56 63
8の位:  8 16 24 32 40 48 56 64 72
9の位:  9 18 27 36 45 54 63 72 81

多重ループは、内側のループから繰り返し処理が行われます。
ループごとのそれぞれの変数の値を書きだすと、

  • i: 1, j: 1
  • i: 1, j: 2
  • i: 1, j: 3
  • i: 1, j: 4
  • ...
  • i: 1, j: 8
  • i: 1, j: 9

次のループで変数jの値は10となり、j < 10の条件に合わなくなるので内側のループは終了します。
改行文字を出力した後、外側のループの先頭に戻り、変数iの値を1増やします。

その後、また内側のループを9回行います。

  • i: 2, j: 1
  • i: 2, j: 2
  • i: 2, j: 3
  • i: 2, j: 4
  • ...
  • i: 2, j: 8
  • i: 2, j: 9

これを全部で9 × 9 = 81回繰り返し、ループは終了します。

ちなみに、printf関数の書式指定文字が%2dとなっていますが、これは出力される文字の桁数を最低2桁にする、という指定です。
見た目を綺麗に揃えるために指定しています。

詳しくはprintf関数を参照してください。

なお、ある構文の中に同じ構文を記述することを「入れ子」や「ネスト」といいます。
上のコードはfor文の入れ子、ネストということです。

多重ループとbreak文

ループ文を制御するbreak文やcontinue文は現在のループに対してのみ有効です。
以下のサンプルコードは入力された文字の内部的な数値を表示します。


#include <stdio.h>

int main()
{
    while (1)
    {
        printf("変換したい文字を入力\n");
        while (1)
        {
            char c = getchar();
            if (c == '\n')
                break;

            printf("%d ", c);
        }

        printf("\n終了しますか?\n");
        printf("y=yes / n=no\n");
        char c = getchar();
        if (c == 'y')
            break;
        if(c != '\n')
            while (getchar() != '\n');
        printf("\n");
    }
}

8~15行目が多重ループです。
(一応23行目も多重ループです)
12行目のbreak文が実行されると、処理は17行目に移ります。
その後のプログラムの終了確認で「y」を押さなければ、外側のループにより処理は7行目に戻ります。
多重ループの場合にbreak文で抜けられるのは、内側のループだけです。

多重ループを一気に抜ける

場合によっては多重ループを一気に抜けたい場合があります。
しかしC言語にはそのような機能はありません。

多重ループを一気に抜けるにはいくつかの方法があります。

goto文を使う

C言語にはgoto文というものあります。
これはラベルというものをあらかじめコード内に指定し、その行にコードの処理をジャンプさせる機能です。


#include <stdio.h>

int main()
{
    while (1)
    {
        printf("外側ループ開始\n");
        while (1)
        {
            printf("内側ループ開始\n");
            goto loopend;
        }
    }
loopend:
    printf("プログラム終了\n");
    getchar();
}

上のコードは、11行目のgoto文で「loopend」というラベルに処理を移すように指定しています。
ラベルはラベル名の最後にコロン(:)を書くことで指定します。
「loopend」ラベルは14行目にあるため、多重ループを一気に抜けてプログラムを終了します。

goto文は多様するとプログラムの処理の流れがわかりにくくなるため、あまり推奨されない構文です。
しかし多重ループを抜ける場合は最も簡潔に書けることから、例外的に使用されることがあります。

フラグで終了判定する

ループ終了の判定用の変数(フラグ)を用意し、if文で毎回チェックする方法もあります。
こちらのほうがメジャーでしょう。


#include <stdio.h>

int main()
{
    int flag = 0;
    while (1)
    {
        printf("外側ループ開始\n");
        while (1)
        {
            printf("内側ループ開始\n");
            flag = 1;
            if (flag)
                break;
        }
        if (flag)
            break;
    }
    printf("プログラム終了\n");
    getchar();
}

変数flagがループ終了判定用の変数です。
内側ループで変数flagに0以外の数値を代入すると、if文によりまず内側のループを抜けます。
その直後に同じく外側ループ脱出用のif文を用意することで、多重ループを抜けることができます。

この方法はわかりやすいですが、チェック用変数を用意する必要があることと、ループごとに毎回条件判定を行う必要があるので、実行速度がやや劣ります。

関数化する

多重ループを関数化すると、return文で一気に多重ループを抜けることができます。


#include <stdio.h>

void loop()
{
    while (1)
    {
        printf("外側ループ開始\n");
        while (1)
        {
            printf("内側ループ開始\n");
            return;
        }
    }
}

int main()
{
    loop();
    printf("プログラム終了\n");
    getchar();
}

return文を実行すると直ちに関数を終了することができますから、多重ループの内側であっても一気に終了することができます。

この方法の欠点は、わざわざ関数化しなければならないという点でしょう。
関数の呼び出しはわずかに速度に影響があります。
また、多重ループが増えてくるとコードが複雑化し、どの関数を呼び出しているのかが分かりにくくなることがあります。

終了条件をいじる

終了条件に変数がセットされている場合は、終了条件を満たすように変数の値を書き換えることで多重ループを抜けることもできます。


#include <stdio.h>

int main()
{
    int loopX = 10;
    int loopY = 10;
    for (int x = 0; x < loopX; x++)
    {
        printf("外側ループ開始\n");
        for (int y = 0; y < loopY; y++)
        {
            printf("内側ループ開始\n");
            x = loopX;
            y = loopY;
        }
    }
    printf("プログラム終了\n");
    getchar();
}

for文のループの終了条件が「x < 10」ならば、xの値を10以上にすればループは終了します。
同様に内側のループの終了条件も満たすように変数をセットすれば、多重ループを一気に抜けることができます。
ただし内側のループの直後に何かしらの処理がある場合はそれをスキップすることはできません。