~何度も実行する処理を効率よく記述する~

なぜ、繰り返し処理の理解が重要なのか、その理由

この記事では、当社の新人エンジニア研修の参考にC#を解説します。

前回は「条件開発者の三種の神器① 条件分岐分岐」について学びました。

今回は繰り返し処理(ループ処理)について解説します。人間だったら退屈になってしまうような反復作業も、コンピュータにとっては得意分野です。例えば信号機のように、一日中「赤 → 黄 → 青」を繰り返し続けるのも苦になりません。

C#では、主に以下3つの繰り返し構文を学習します。

  1. while
  2. for
  3. do ... while

繰り返し処理は、開発者の三種の神器のうち、二つ目の要素です。繰り返しの使いどころをご紹介して、その重要性をお伝えしようと思います。

一般に、アプリケーションでは同じ操作を大量のデータに対して行う、という処理を実装することが多々あります。例えば学校の生徒全員を年度末に進級させる、ECサイトの会員全員にイベントで100ポイントを付与する、というような場合です。この処理を、一人一人に対して何度も人数分書き続けたらどうなるでしょうか。会員が1000人いれば、1000回分のコードを書かなければならず、その結果とてつもなく長いプログラムができあがります。しかも延々と書いてあるのが同じような処理ばかりと来れば、可読性もメンテナンス性もあったものではありません。

しかし、ポイント付与の処理を一つだけ書き、それを会員数分だけ繰り返すようにすれば、どうでしょうか。コードも短く、読みやすくなり、もし処理が変更になったとしても直すのは一箇所だけで済みますね。これが繰り返し処理の有用性です。では早速、実際の構文を見てみましょう。

ソリューションに、プロジェクトChap05を追加して、サンプルコードを実行する準備をしてください。

1. while

英語の while は「~の間」という意味です。

構文

while (継続条件式) {
    // 文(処理内容)
}
  1. 「継続条件式」が true なら中の処理を実行し、再度条件式を評価 → true ならまた繰り返し
  2. false ならループを終了します。
namespace Chap05;
using System;

public class Example01 {
    public static void run() {
        int i = 0;
        while (i < 3) {
            Console.WriteLine(i);
            i++;
        }
    }
}

<実行結果>

0
1
2
  1. i が3になるまで i < 3true のため、コンソールに 0, 1, 2 と出力します。
  2. もし i++ を書き忘れると、i がずっと0のままなので i < 3 は永遠に true となり、無限ループに陥ります。Visual Studioやコマンドプロンプトで無限ループが起きた際の停止方法を覚えておきましょう。

2. for

while文と同様の処理をする場合でも、変数の初期化・条件式・更新処理を1行にまとめられるのがfor文です。英語の for にも「(期間や回数)~の間」というニュアンスがあります。

以下の図のようにwhile文と全く同じことをするにもコンパクトにかけるという特徴があります。

新人エンジニア
while文とfor文の比較

構文

for (初期化式; 継続条件式; 更新式) {
    // 文(処理内容)
}
  1. ループに入るに、初期化式を1度だけ実行
  2. 継続条件式が true なら中の文を実行し、実行後に更新式を行う
  3. 継続条件式が false になったらループを終了

下図の順番をしっかり頭に入れましょう。

新人エンジニア
for文の構造
namespace Chap05;
using System;

public class Example02 {
    public static void run() {
        for (int i = 0; i < 3; i++) {
            Console.WriteLine(i);
        }
    }
}

<実行結果>

0
1
2

  • int i = 0;i を初期化 → i < 3true{ ... } 実行 → i++ → 再度 i < 3判定… を繰り返します。
  • 処理内容は while版と同じですが、初期化・条件式・更新がまとまっており、可読性が高いのが利点です。

例題

次のfor文の繰り返し回数を答えなさい。

for (int i = 0; i < 100; i++) {}
for (int i = 1; i < 100; i++) {}
for (int i = 0; i <= 100; i++) {}
for (int i = 1; i <= 100; i++) {}
for (int i = 0; i < 100; i+=2) { }
for (int i = 100; i > 0; i--) { }

for文と変数スコープ

for (int i = 0; i < 6; i++) {
    Console.WriteLine(i);
}
Console.WriteLine(i); // ここでiを参照できるか?
  • 上記はコンパイルエラーになります。iforブロックの中で宣言されたため、ループ処理が終わってしまえば破棄されて使えなくなってしまうからです。
  • もしループの外でもiを使いたいなら、あらかじめ int i; を書いてから for (i = 0; i < 6; i++) とするなどが必要です。
int i;
for (i = 0; i < 6; i++) {
    Console.WriteLine(i);
}
Console.WriteLine(i); // こうすればエラーにはなりません

3. ユーザー入力を受け付ける例

C#では主に Console.ReadLine() を使いユーザー入力を受け付けます。入力は文字列として受け取り、数値として使いたい場合は int.Parse() などで変換します。

例:ユーザーが入力した回数ぶんカウントアップする

namespace Chap05;
using System;

public class Example03 {
    public static void run() {
        Console.WriteLine("何回数え上げますか?");
        // キーボードから読み込む (文字列)
        string line = Console.ReadLine();
        // 文字列→整数に変換
        int count = int.Parse(line);

        int i = 0;
        while (i < count) {
            Console.WriteLine(i);
            i++;
        }
    }
}

<実行イメージ>

何回数え上げますか?
5
0
1
2
3
4

4. ループを中断する break

処理上の都合で、ある条件下ではfor文やwhile文の条件式とは別に、繰り返し処理を中断したい時があります。そのような場合に使えるのが break 文です。前章の条件分岐構文と組み合わせれば、「このような場合にはループ処理を抜ける」という処理を書くことが出来ます。以下の例ではわかりやすくするため、意図的に作った無限ループを抜けるやり方をご紹介しています。

namespace Chap05;
using System;

public class Example04 {
    public static void run() {
        int sum = 0;
        while (true) {
            Console.Write("整数値を入力してください (終了は -1):");
            string line = Console.ReadLine();
            int n = int.Parse(line);

            if (n == -1) {
                break; // whileループを終了
            }

            sum += n;
            Console.WriteLine($"現在の合計: {sum}");
        }
    }
}

<実行例>

整数値を入力してください (終了は -1):2
現在の合計: 2
整数値を入力してください (終了は -1):4
現在の合計: 6
整数値を入力してください (終了は -1):-1
(ここでループ終了)
  1. n == -1 という終了条件が真になった瞬間、ループを強制的に抜けます。
  2. for(;;) と書いてもC#では無限ループとして機能します。

実務では、無限ループではなくwhileやforの条件式はあるけれど、繰り返し処理中に生じる何らかの条件でループを終了させたい場合に、break 文を使うことが多いです。例えば、町内の子供達のデータが年齢順に並んでいて、16才未満の子にはお菓子を配る、という処理では、while文の条件式は町内の子供の数まで繰り返す、に設定し、ループ処理内で、処理対象の子供の年齢が16になった時点でbreak すれば良い、ということです。それ以後は、16才以上のデータしか無いことが明らかだからです。

5. ループを1回スキップする continue

continue 文は、現在実行中のループ処理を中断するのはbreak文と同じですが、繰り返しを終了してループを抜けるのでは無く、次のループ処理の先頭(厳密にはループ継続の判定処理)にジャンプする命令です。if文で同じことは書けるので、使いすぎると読みづらい場合もあります。

namespace Chap05;
using System;

public class Example05 {
    public static void run() {
        int sum = 0;
        for (int i = 0; i < 3; i++) {
            Console.Write("整数値を入力してください: ");
            int n = int.Parse(Console.ReadLine());
            if (n < 0) {
                // 負数ならスキップ
                continue;
            }
            sum += n;
            Console.WriteLine($"入力された数値: {n}");
            Console.WriteLine($"現在の合計: {sum}");
        }
        Console.WriteLine("終わります。");
    }
}

<実行例>

整数値を入力してください: 2
入力された数値: 2
現在の合計: 2
整数値を入力してください: -1
(ここで continue → この回の残り処理をスキップ)
整数値を入力してください: 2
入力された数値: 2
現在の合計: 4
終わります。

continue 文と同じ処理をif文でも書ける、というのはこういうことです。以下のサンプルコードをご覧ください。


namespace Chap05;
using System;

public class Example06 {
    public static void run() {
        int sum = 0;
        for (int i = 0; i < 3; i++) {
            Console.Write("整数値を入力してください: ");
            int n = int.Parse(Console.ReadLine());
            if (0 <= n) {
                // 0か正数なら処理を行う
                sum += n;
                Console.WriteLine($"入力された数値: {n}");
                Console.WriteLine($"現在の合計: {sum}");
            }
        }
        Console.WriteLine("終わります。");
    }
}

if文の条件式を反転してループ時の処理をif文のブロック内に移動させることで、continue 文を書かなくても同じ処理が実行できていますね。

continue 文の実務上の使いどころを、break 文の時の例を使って解説します。町内の子供達のデータが年齢順ではなく、年齢がバラバラに並んでいる場合を考えてみましょう。ループ内の処理は、16才未満の子にはお菓子を配る、while文の条件式は町内の子供の数まで繰り返す、に設定してあるとします。年齢がバラバラのデータですから、ループ処理は最後まで実行しなければなりません。break 文でループを終了してしまってはダメです。しかし、16才以上の子の場合には、お菓子を配るという処理を行わずに次の子の繰り返し処理を実行しなければなりません。このような時に、continueを使って、16才以上の子の場合には次の子の処理を行うために、ループの先頭に処理を移すのです。

6. ループのネスト

ループの中にさらに別のループを入れることを「ループのネスト」「入れ子処理」と呼びます。典型的な例は「九九表」のように、2重ループで i=1..9j=1..9 を組み合わせる場合です。

namespace Chap05;
using System;

public class Example07 {
    public static void run() {
       for (int i = 1; i <= 9; i++) {
           for (int j = 1; j <= 9; j++) {
               Console.Write($"{i * j}\t"); // タブ区切り
           }
            Console.WriteLine(); // 改行
        }
    }
}

<実行結果>

1 2 3 … 9
2 4 6 … 18
… (以下略)

  • 外側のループ(青色)が9回、内側のループ(オレンジ色)もそれぞれ9回まわるため、合計81回の掛け算が行われます。
新人エンジニア

ループのネストのイメージ

7. do ... while

do ... while 文は、必ず1回は処理を実行してから、継続判定を行う構文です。一回処理を行わないと次のループ処理を実行すべきかがわからない、という場合に用います。例えば、ゲームで10人抜きの対戦を行う場合を考えてみましょう。

構文

do {
    // 対戦処理
} while (継続条件:対戦に勝てば);

このような使い方をします。まずは対戦してみないと次の対戦ができるかわかりませんよね。

  1. whileの後ろに セミコロン(;) を付ける点に注意
  2. { ... } を少なくとも1回は実行し、実行後に 継続条件式 を評価 → true なら再度ループ、false なら終了
namespace Chap05;
using System;

public class Example08 {
    public static void run() {
        int count = 0;
        do {
            count++;
        } while (count < 0);
        Console.WriteLine(count);
    }
}

<実行結果>

1

  • count が0から始まり、do { count++ } while (count < 0) の判定をするときは既に1なので count < 0false → 1回だけ実行されて終了、結果は1が表示されます。
  • 初学者がwhile(...)の後にセミコロンを書いてしまうミスは、普通のwhile文でやると空文になってしまうので注意しましょう。

<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

□ 繰り返しは、プログラマーの三種の神器の一つであり、多量のデータに同様の処理を行う場合に力を発揮する

while文やfor文は「継続条件がtrueの間、処理を繰り返す」構文である

for(初期化; 条件; 更新) では「条件式は繰り返し処理の前に判定」、「更新式は繰り返し処理の後に実行」される

break文はループを中断し、continue文はループを1回スキップする

□ 終了条件と継続条件の関係を論理否定で相互に変換できる

□ ループの中にループを入れるのを「ループのネスト」と呼ぶ

次回は、「6章. 開発者の三種の神器③ 配列とコレクション」を学びます。

最後までお読みいただきありがとうございます。