第6章:繰り返しの力

この章で学ぶこと

  • ループ(繰り返し)の概念
  • while文
  • for文
  • do-while文
  • ループ制御(break, continue)
  • 無限ループ
  • ネストしたループ
  • ---

    6.1 ループとは

    同じ処理を繰り返す

    「1から10までの数を表示する」プログラムを考えてみましょう。

    ループを使わない場合:

    printf("1\n");
    printf("2\n");
    printf("3\n");
    printf("4\n");
    printf("5\n");
    printf("6\n");
    printf("7\n");
    printf("8\n");
    printf("9\n");
    printf("10\n");
    

    10行も書く必要があります。100まで表示するなら100行、1000なら...想像するだけで大変です。

    ループを使う場合:

    for (int i = 1; i <= 10; i++) {
        printf("%d\n", i);
    }
    

    たった3行で済みます。1000回でも同じです。

    ループの種類

    C言語には3種類のループがあります:

  • while - 条件が真の間、繰り返す
  • for - 決まった回数だけ繰り返す
  • do-while - 最低1回は実行し、条件が真の間、繰り返す

---

6.2 while文

基本形

while (条件) {
    // 条件が真の間、繰り返し実行される
}

条件が真(true)の間、波括弧内の処理が繰り返し実行されます。

使用例

#include <stdio.h>

int main(void)
{
    int count = 1;

    while (count <= 5) {
        printf("Count: %d\n", count);
        count++;  // カウンターを増やす
    }

    printf("Done!\n");
    return 0;
}

出力:

Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Done!

実行の流れ

  • count = 1
  • count <= 5 → 1 <= 5 → 真 → ループ内を実行
  • "Count: 1" を表示
  • count++count = 2
  • count <= 5 → 2 <= 5 → 真 → ループ内を実行
  • ... (繰り返し)
  • count = 6 のとき、count <= 5 → 偽 → ループ終了
  • "Done!" を表示
  • while文のポイント

  • 初期化: ループに入る前にカウンター変数を初期化
  • 条件: いつか必ず偽になる条件を設定
  • 更新: ループ内でカウンターを変更
  • これらを忘れると無限ループになります。

    ---

    6.3 for文

    基本形

    for (初期化; 条件; 更新) {
        // 条件が真の間、繰り返し実行される
    }
    

  • 初期化: ループ開始前に1回だけ実行
  • 条件: 毎回ループ前にチェック
  • 更新: 毎回ループ後に実行

使用例

#include <stdio.h>

int main(void)
{
    for (int i = 1; i <= 5; i++) {
        printf("Count: %d\n", i);
    }

    printf("Done!\n");
    return 0;
}

出力(whileの例と同じ):

Count: 1
Count: 2
Count: 3
Count: 4
Count: 5
Done!

forとwhileの比較

// for文
for (int i = 0; i < 10; i++) {
    printf("%d\n", i);
}

// 同等のwhile文
int i = 0;
while (i < 10) {
    printf("%d\n", i);
    i++;
}

forは「初期化・条件・更新」が1行にまとまっているため、繰り返し回数が決まっている場合に適しています。

様々なforの使い方

// 0から9まで(10回)
for (int i = 0; i < 10; i++) { }

// 1から10まで(10回)
for (int i = 1; i <= 10; i++) { }

// 10から1までカウントダウン
for (int i = 10; i >= 1; i--) { }

// 2ずつ増加
for (int i = 0; i < 10; i += 2) { }

// 複数の変数
for (int i = 0, j = 10; i < j; i++, j--) { }

カウンター変数の名前

ループのカウンター変数には、慣習的に i, j, k がよく使われます。

for (int i = 0; i < 10; i++) { }     // 1つ目のループ
for (int j = 0; j < 10; j++) { }     // ネストした2つ目
for (int k = 0; k < 10; k++) { }     // ネストした3つ目

ただし、意味のある名前を使う方が良い場合もあります:

for (int row = 0; row < height; row++) {
    for (int col = 0; col < width; col++) {
        // ...
    }
}

---

6.4 do-while文

基本形

do {
    // 最低1回は実行される
} while (条件);  // セミコロンに注意!

do-while条件チェックが後に行われます。そのため、ループ内の処理が最低1回は実行されることが保証されます。

使用例

#include <stdio.h>

int main(void)
{
    int count = 1;

    do {
        printf("Count: %d\n", count);
        count++;
    } while (count <= 5);

    printf("Done!\n");
    return 0;
}

whileとdo-whileの違い

int x = 10;

// while: 条件が最初から偽なら、1回も実行されない
while (x < 5) {
    printf("while: %d\n", x);
    x++;
}
// 何も表示されない

// do-while: 最低1回は実行される
do {
    printf("do-while: %d\n", x);
    x++;
} while (x < 5);
// "do-while: 10" が表示される

do-whileの典型的な使用場面

入力の検証によく使われます:

int num;

do {
    printf("Enter a positive number: ");
    scanf("%d", &num);
} while (num <= 0);

printf("You entered: %d\n", num);

ユーザーが正しい入力をするまで、繰り返し入力を求めます。

---

6.5 break文

ループを途中で抜ける

break を使うと、ループを即座に終了できます。

for (int i = 1; i <= 10; i++) {
    if (i == 5) {
        break;  // i が 5 になったらループを抜ける
    }
    printf("%d\n", i);
}
printf("Loop ended.\n");

出力:

1
2
3
4
Loop ended.

i が 5 になった時点で break が実行され、ループを抜けます。

検索の例

#include <stdio.h>

int main(void)
{
    int numbers[] = {10, 25, 8, 42, 17, 33};
    int target = 42;
    int found = 0;

    for (int i = 0; i < 6; i++) {
        if (numbers[i] == target) {
            printf("Found %d at index %d\n", target, i);
            found = 1;
            break;  // 見つかったらループを抜ける
        }
    }

    if (!found) {
        printf("%d not found\n", target);
    }

    return 0;
}

---

6.6 continue文

残りの処理をスキップ

continue を使うと、現在のループの残りの処理をスキップし、次の繰り返しに進みます。

for (int i = 1; i <= 5; i++) {
    if (i == 3) {
        continue;  // i が 3 のときはスキップ
    }
    printf("%d\n", i);
}

出力:

1
2
4
5

i が 3 のとき、continue により printf がスキップされます。

偶数のみ表示

for (int i = 1; i <= 10; i++) {
    if (i % 2 != 0) {
        continue;  // 奇数はスキップ
    }
    printf("%d\n", i);
}

出力:

2
4
6
8
10

breakとcontinueの違い

// break: ループ全体を終了
for (int i = 1; i <= 5; i++) {
    if (i == 3) break;
    printf("%d ", i);
}
// 出力: 1 2

// continue: 現在の繰り返しのみスキップ
for (int i = 1; i <= 5; i++) {
    if (i == 3) continue;
    printf("%d ", i);
}
// 出力: 1 2 4 5

---

6.7 無限ループ

意図的な無限ループ

条件が常に真になるループは無限ループです。

while (1) {
    // 永遠に実行される
}

for (;;) {
    // 永遠に実行される
}

無限ループは break と組み合わせて使います:

while (1) {
    char input;
    printf("Continue? (y/n): ");
    scanf(" %c", &input);

    if (input == 'n' || input == 'N') {
        break;
    }

    printf("Processing...\n");
}

意図しない無限ループ

無限ループはバグの原因にもなります:

// バグ!カウンターが更新されていない
int i = 0;
while (i < 10) {
    printf("%d\n", i);
    // i++ を忘れた
}

// バグ!条件が常に真
for (int i = 0; i >= 0; i++) {
    printf("%d\n", i);
}

無限ループに陥ったら、Ctrl + C でプログラムを強制終了できます。

---

6.8 ネストしたループ

ループの中にループ

ループの中に別のループを入れることをネスト(入れ子)と呼びます。

for (int i = 1; i <= 3; i++) {
    for (int j = 1; j <= 3; j++) {
        printf("i=%d, j=%d\n", i, j);
    }
}

出力:

i=1, j=1
i=1, j=2
i=1, j=3
i=2, j=1
i=2, j=2
i=2, j=3
i=3, j=1
i=3, j=2
i=3, j=3

外側のループが1回実行されるごとに、内側のループが全回数実行されます。

九九の表

#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;
}

出力:

  1  2  3  4  5  6  7  8  9
  2  4  6  8 10 12 14 16 18
  3  6  9 12 15 18 21 24 27
  4  8 12 16 20 24 28 32 36
  5 10 15 20 25 30 35 40 45
  6 12 18 24 30 36 42 48 54
  7 14 21 28 35 42 49 56 63
  8 16 24 32 40 48 56 64 72
  9 18 27 36 45 54 63 72 81

三角形の描画

#include <stdio.h>

int main(void)
{
    int height = 5;

    for (int i = 1; i <= height; i++) {
        for (int j = 0; j < i; j++) {
            printf("*");
        }
        printf("\n");
    }
    return 0;
}

出力:

*
**
***
****
*****

ネストしたループとbreak

ネストしたループで break を使うと、内側のループだけが終了します。

for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        if (j == 1) {
            break;  // 内側のループだけを抜ける
        }
        printf("i=%d, j=%d\n", i, j);
    }
}

出力:

i=0, j=0
i=1, j=0
i=2, j=0

外側のループも抜けたい場合は、フラグ変数を使うか、goto を使います(一般的にはフラグ変数が推奨)。

---

6.9 実践:素数判定

素数とは

1と自分自身以外に約数を持たない、1より大きい自然数。

2, 3, 5, 7, 11, 13, 17, 19, 23, ...

実装

#include <stdio.h>

int main(void)
{
    int num;

    printf("Enter a positive integer: ");
    scanf("%d", &num);

    if (num <= 1) {
        printf("%d is not a prime number.\n", num);
        return 0;
    }

    int is_prime = 1;  // 素数と仮定

    for (int i = 2; i * i <= num; i++) {
        if (num % i == 0) {
            is_prime = 0;  // 割り切れたら素数ではない
            break;
        }
    }

    if (is_prime) {
        printf("%d is a prime number.\n", num);
    } else {
        printf("%d is not a prime number.\n", num);
    }

    return 0;
}

なぜ i i <= num なのか

約数はペアで存在します。例えば 36 の場合:

  • 1 × 36
  • 2 × 18
  • 3 × 12
  • 4 × 9
  • 6 × 6

6 以上の約数を調べる必要はありません(すでにペアとして見つかっている)。つまり、平方根までチェックすれば十分です。

i <= sqrt(num) よりも i i <= num の方がライブラリ不要で効率的です。

---

6.10 実践:FizzBuzz

問題

1から100までの数を表示する。ただし:

  • 3の倍数のときは "Fizz"
  • 5の倍数のときは "Buzz"
  • 3と5の両方の倍数のときは "FizzBuzz"
  • 実装

    #include <stdio.h>
    
    int main(void)
    {
        for (int i = 1; i <= 100; i++) {
            if (i % 3 == 0 && i % 5 == 0) {
                printf("FizzBuzz\n");
            } else if (i % 3 == 0) {
                printf("Fizz\n");
            } else if (i % 5 == 0) {
                printf("Buzz\n");
            } else {
                printf("%d\n", i);
            }
        }
        return 0;
    }
    

    出力(一部)

    1
    2
    Fizz
    4
    Buzz
    Fizz
    7
    8
    Fizz
    Buzz
    11
    Fizz
    13
    14
    FizzBuzz
    ...
    

    FizzBuzzは、プログラマーの採用面接でよく出される問題です。単純そうに見えて、意外と間違える人が多いです。

    ---

    6.11 この章のまとめ

    学んだこと

  • while文
- 条件が真の間、繰り返す - 条件チェックが先

  • for文
- 初期化・条件・更新が1行に - 回数が決まっている繰り返しに最適

  • do-while文
- 最低1回は実行される - 条件チェックが後

  • break
- ループを途中で抜ける

  • continue
- 現在の繰り返しをスキップ

  • 無限ループ
- while(1) または for(;;) - break と組み合わせて使用

  • ネストしたループ
- ループの中にループ

ループの選び方

| 状況 | 推奨するループ | |------|----------------| | 回数が決まっている | for | | 条件が満たされるまで | while | | 最低1回は実行したい | do-while | | 途中で抜ける可能性 | while + break |

次の章の予告

次章では、関数を学びます。処理をまとめて再利用する方法を覚えましょう。

---

Column: ループの名前の由来

for, while, do の語源

プログラミング言語のキーワードは、英語の単語から来ています。

  • for: 「〜の間」「〜のために」
- "for each item" (各項目に対して)

  • while: 「〜の間」
- "while the condition is true" (条件が真の間)

  • do: 「する」「行う」
- "do this" (これを行う)

これらの言葉は1960年代のALGOLという言語で導入され、C言語を経て、現代のほとんどの言語に受け継がれています。

goto とスパゲッティコード

昔のプログラミング言語には goto 文しかありませんでした。

// 昔のスタイル(推奨されない)
int i = 0;
loop:
    if (i >= 10) goto end;
    printf("%d\n", i);
    i++;
    goto loop;
end:
    printf("Done\n");

このように goto を多用すると、コードがスパゲッティのように絡まり合い、読みにくくなります。

構造化されたループ(for, while, do-while)の登場により、コードははるかに読みやすくなりました。

// 現代のスタイル
for (int i = 0; i < 10; i++) {
    printf("%d\n", i);
}
printf("Done\n");

---

確認問題

問題1

以下のコードの出力は何ですか?

for (int i = 0; i < 5; i++) {
    printf("%d ", i);
}

解答

0 1 2 3 4

i は 0 から始まり、5 未満の間(0, 1, 2, 3, 4)繰り返す。

問題2

以下のwhileループをfor文に書き換えてください。

int i = 10;
while (i >= 0) {
    printf("%d ", i);
    i--;
}

解答

for (int i = 10; i >= 0; i--) {
    printf("%d ", i);
}

問題3

以下のコードの出力は何ですか?

for (int i = 0; i < 5; i++) {
    if (i == 2) continue;
    if (i == 4) break;
    printf("%d ", i);
}

解答

0 1 3

  • i=0: 表示
  • i=1: 表示
  • i=2: continue でスキップ
  • i=3: 表示
  • i=4: break でループ終了

問題4

1から100までの整数の合計を計算するプログラムを書いてください。

解答

int sum = 0;
for (int i = 1; i <= 100; i++) {
    sum += i;
}
printf("Sum: %d\n", sum);  // Sum: 5050

問題5

5段の逆三角形を描画するプログラムを書いてください。

*****
****
***
**
*

解答

for (int i = 5; i >= 1; i--) {
    for (int j = 0; j < i; j++) {
        printf("*");
    }
    printf("\n");
}

---

次の章では、関数を学びます。コードを整理し、再利用する技術を身につけましょう!