【C#】もう困らない!for文の簡単な使い方

はい、承知いたしました。
C#のfor文について、初心者の方が「もう困らない!」と感じられるよう、基本から応用まで、非常に詳細な説明を含む約5000語の記事を作成します。

以下に記事の内容を直接表示します。


もう困らない!C# for文の徹底解説 – 基本から応用まで

プログラミングの世界へようこそ!新しい言語や概念を学ぶとき、最初は難しく感じることも多いでしょう。特に「繰り返し」の処理は、プログラミングの根幹をなす非常に重要な概念ですが、最初は戸惑うかもしれません。

この記事では、C#における繰り返しのための最も基本的な構文の一つである「for文」に焦点を当て、その使い方を徹底的に解説します。「もうfor文で困らない!」と感じられるレベルまで、基本から応用、さらにはよくある落とし穴や効率的な使い方まで、懇切丁寧に説明していきます。

もしあなたが、

  • for文の書き方がよくわからない
  • for文のカッコの中の意味が理解できない
  • 配列やリストをfor文で処理する方法を知りたい
  • 複雑な繰り返し処理をどう書けばいいか悩んでいる
  • for文でよくエラーが出る、あるいは意図した動きにならない

といった悩みを持っているなら、この記事がきっとあなたの助けになるはずです。一緒にfor文をマスターして、プログラミングの幅を広げましょう!

第1章:なぜ繰り返し処理が必要なのか? – ループの概念

プログラムは、コンピュータに指示を与えるためのものです。多くの場合、同じような作業を何度も繰り返す必要があります。例えば、

  • 1から100までの数字を画面に表示する。
  • 商品のリストから、値段が1000円以上のものをすべて探し出す。
  • ゲームでキャラクターを10歩前進させる。
  • ファイルから読み込んだデータの各行を処理する。

このような「同じような操作を複数回行う」という状況は、プログラミングでは日常茶飯事です。もし、繰り返し処理の仕組みがなければ、どうなるでしょうか?

例えば、1から5までの数字を画面に表示するだけなら、

csharp
Console.WriteLine(1);
Console.WriteLine(2);
Console.WriteLine(3);
Console.WriteLine(4);
Console.WriteLine(5);

このように、5行書けば実現できます。では、1から100までを表示するにはどうでしょう? Console.WriteLine() を100行書く必要があります。もし1から1000までなら? 1000行です。これは非現実的ですし、コードも読みにくく、修正も大変になります。

ここで登場するのがループ(Loop)という概念です。ループは、「特定の処理のブロックを、ある条件が満たされている間、あるいは特定の回数だけ繰り返す」ための構文です。ループを使うことで、たった数行のコードで、何回でも、あるいは様々な条件に基づいて繰り返し処理を実行できるようになります。

C#にはいくつかのループの種類があります。

  • for文:回数が決まっている場合や、特定の範囲でインデックス(順番)を使いながら繰り返したい場合に特に適しています。
  • while文:特定の条件が真である限り繰り返したい場合に適しています。回数は事前に決まっていないことが多いです。
  • do-while文:while文と似ていますが、最低1回は必ず処理を実行したい場合に適しています。
  • foreach文:配列やコレクション(データの集まり)の要素を、先頭から順に取り出しながら処理したい場合に特に適しています。

この記事では、これらのループの中でも最も基本的で、かつ非常に汎用性の高いfor文に焦点を当てて徹底的に解説します。

第2章:C# for文の基本構造を理解する

それでは、C#のfor文の書き方とその構造を見ていきましょう。

for文は、以下の基本的な構文を持ちます。

csharp
for (初期化式; 条件式; 更新式)
{
// 繰り返したい処理(ループ本体)
}

この構文には、大きく分けて4つの部分があります。

  1. forキーワード: ループの開始を示します。
  2. 丸カッコ (): ループの制御に関する情報を記述します。この中に「初期化式」「条件式」「更新式」の3つが入ります。
  3. セミコロン ;: 丸カッコ内の3つの式を区切ります。
  4. 波カッコ {}: 繰り返したい処理のブロックです。これをループ本体と呼びます。処理が1行だけの場合は波カッコを省略することも可能ですが、可読性や安全性の観点から、常に波カッコを付けることが強く推奨されます。

それぞれの部分がどのような役割を果たすのかを詳しく見ていきましょう。

2.1. 初期化式 (Initializer)

for文が始まる前に、一度だけ実行される部分です。主に、ループの繰り返し回数を数えるためのループカウンタ変数を宣言したり、初期値を設定したりするために使われます。

例:int i = 0;

これは、「iという名前の整数型の変数を宣言し、その初期値を0にする」という意味です。この変数iは、通常、ループが何回実行されたか、あるいは処理対象のインデックスがいくつであるかを示すために使用されます。

  • 複数の変数を初期化することも可能です。その場合はカンマ , で区切ります。
    例:int i = 0, j = 10;
  • すでに宣言済みの変数をここで初期化(値を再設定)することも可能です。
    例:int count; for (count = 0; ...)

ここで宣言された変数は、通常、そのfor文のブロック({}で囲まれた範囲)内でのみ有効なローカル変数となります。

2.2. 条件式 (Condition)

ループ本体が実行される前に、毎回評価される式です。この式がtrueである限り、ループ本体が実行されます。式がfalseになった時点で、ループは終了し、for文の直後の処理に移ります。

例:i < 10;

これは、「変数iの値が10より小さいか?」という条件です。iが0, 1, 2, …, 9 の間は条件がtrueとなりループが実行されますが、iが10になった時点で条件がfalseとなりループは終了します。

  • 条件式は、真 (true) または偽 (false) を返すブール式である必要があります。
  • 比較演算子(<, >, <=, >=, ==, !=)や論理演算子(&&, ||, !)などを組み合わせて複雑な条件を指定することも可能です。

もし、for文の開始時点でこの条件式がすでにfalseであった場合、ループ本体は一度も実行されずにスキップされます。

2.3. 更新式 (Iterator)

ループ本体が実行された、次に条件式を評価するに、毎回実行される部分です。主に、ループカウンタ変数の値を変更するために使われます。これにより、繰り返しが進むにつれて条件式の結果が変化し、最終的にループが終了するようになります。

例:i++;

これは、「変数iの値を1増やす」という意味です(インクリメント演算子)。

  • i++ (1増やす) や i-- (1減らす) がよく使われます。
  • i += 2 (2増やす), i -= 5 (5減らす), i *= 2 (2倍にする) など、複合代入演算子もよく使われます。
  • 複数の式をカンマ , で区切って記述することも可能です。
    例:i++, j--
  • 任意の式を記述できますが、通常はループの進行に関わる変数の更新を行います。

この更新式によってループ変数の値が適切に変化しないと、条件式がいつまでもtrueのままになり、無限ループに陥る可能性があるので注意が必要です。

2.4. ループ本体 (Loop Body)

波カッコ {} の中に書かれた、実際に繰り返したい処理です。条件式がtrueと評価されるたびに、このブロック内のコードが上から順に実行されます。

例:Console.WriteLine(i);

これは、「変数iの現在の値を画面に表示する」という処理です。

第3章:for文の実行フローを追ってみよう

先ほどの基本構造を踏まえて、簡単なfor文がどのように実行されるのか、その流れを具体的に追ってみましょう。

例:1から5までの数字を画面に表示するfor

csharp
for (int i = 1; i <= 5; i++)
{
Console.WriteLine(i);
}
Console.WriteLine("ループ終了"); // ループの後に実行される

このコードの実行フローは以下のようになります。

  1. 初期化式を実行: int i = 1; が実行されます。変数iが宣言され、値が1に設定されます。これはループの開始時に一度だけ行われます。

    • 現在のiの値: 1
  2. 条件式を評価: i <= 5 が評価されます。現在のiの値は1なので、1 <= 5trueです。

    • 条件式の評価結果: true
  3. 条件式がtrueなので、ループ本体を実行: 波カッコ {} の中の Console.WriteLine(i); が実行されます。画面に 1 が表示されます。

  4. 更新式を実行: ループ本体の実行が終わると、i++; が実行されます。iの値が1増えて2になります。

    • 現在のiの値: 2
  5. 再び条件式を評価: i <= 5 が評価されます。現在のiの値は2なので、2 <= 5trueです。

    • 条件式の評価結果: true
  6. 条件式がtrueなので、ループ本体を実行: Console.WriteLine(i); が実行されます。画面に 2 が表示されます。

  7. 更新式を実行: i++; が実行されます。iの値が1増えて3になります。

    • 現在のiの値: 3
  8. 再び条件式を評価: i <= 5 が評価されます。現在のiの値は3なので、3 <= 5trueです。

    • 条件式の評価結果: true
  9. 条件式がtrueなので、ループ本体を実行: Console.WriteLine(i); が実行されます。画面に 3 が表示されます。

  10. 更新式を実行: i++; が実行されます。iの値が1増えて4になります。

    • 現在のiの値: 4
  11. 再び条件式を評価: i <= 5 が評価されます。現在のiの値は4なので、4 <= 5trueです。

    • 条件式の評価結果: true
  12. 条件式がtrueなので、ループ本体を実行: Console.WriteLine(i); が実行されます。画面に 4 が表示されます。

  13. 更新式を実行: i++; が実行されます。iの値が1増えて5になります。

    • 現在のiの値: 5
  14. 再び条件式を評価: i <= 5 が評価されます。現在のiの値は5なので、5 <= 5trueです。

    • 条件式の評価結果: true
  15. 条件式がtrueなので、ループ本体を実行: Console.WriteLine(i); が実行されます。画面に 5 が表示されます。

  16. 更新式を実行: i++; が実行されます。iの値が1増えて6になります。

    • 現在のiの値: 6
  17. 再び条件式を評価: i <= 5 が評価されます。現在のiの値は6なので、6 <= 5falseです。

    • 条件式の評価結果: false
  18. 条件式がfalseなので、ループを終了: for文のブロックから抜け出し、直後の行の処理に移ります。

    • Console.WriteLine("ループ終了"); が実行されます。画面に ループ終了 が表示されます。

このように、初期化式 → (条件式の評価 → ループ本体の実行 → 更新式の実行) の繰り返し → 条件式falseになったら終了、という流れでfor文は実行されます。特に、「更新式はループ本体の後に実行される」「条件式はループ本体の前に(そして最初の初期化式の後に)実行される」という点をしっかり理解することが重要です。

第4章:様々なfor文の使い方と応用

基本構造と実行フローが理解できたところで、様々な繰り返しパターンに対応するためのfor文の使い方を見ていきましょう。

4.1. カウントダウンするfor

これまでは数字を増やしながら繰り返しましたが、減らしながら繰り返すことも簡単です。

csharp
for (int i = 5; i >= 1; i--)
{
Console.WriteLine(i);
}
// 出力:
// 5
// 4
// 3
// 2
// 1

  • 初期化式: int i = 5; (5から始める)
  • 条件式: i >= 1; (iが1以上の間繰り返す)
  • 更新式: i--; (iを1ずつ減らす)

このように、初期値、条件、更新方法を適切に設定することで、様々な範囲や方向でカウントできます。

4.2. 1ずつではなく、指定したステップでカウントする

i++i-- だけでなく、i += 差分i -= 差分 を使うことで、指定したステップで値を増減できます。

例1:2ずつ増やしながら0から10までの偶数を表示

csharp
for (int i = 0; i <= 10; i += 2)
{
Console.WriteLine(i);
}
// 出力:
// 0
// 2
// 4
// 6
// 8
// 10

例2:3ずつ減らしながら100から10までを表示

csharp
for (int i = 100; i >= 10; i -= 3)
{
Console.WriteLine(i);
}
// 出力:
// 100
// 97
// 94
// ...
// 10

4.3. 配列の要素を処理する

for文は、配列の要素に順番にアクセスするのに非常に便利です。配列のインデックスは通常0から始まるため、初期化式を0から始め、条件式で配列の長さを利用するのが一般的です。

“`csharp
string[] fruits = { “Apple”, “Banana”, “Cherry”, “Date” };

for (int i = 0; i < fruits.Length; i++)
{
// 配列名[インデックス] で要素にアクセス
Console.WriteLine($”Index {i}: {fruits[i]}”);
}
// 出力:
// Index 0: Apple
// Index 1: Banana
// Index 2: Cherry
// Index 3: Date
“`

  • 初期化式: int i = 0; (配列の最初のインデックスは0なので)
  • 条件式: i < fruits.Length; (配列の長さより小さい間繰り返す。配列のインデックスは Length - 1 までなので、< が正しい)
  • 更新式: i++; (次の要素へ進むため、インデックスを1増やす)

このように、配列名.Length プロパティを使うことで、配列のサイズが変わってもコードを変更する必要がなくなります。これは、マジックナンバー(意味不明な固定値)を避けるというプログラミングのベストプラクティスにも沿っています。

補足:foreach文について

配列やリストなどのコレクションに対して、「インデックスは必要なく、ただ各要素に順番にアクセスしたい」という場合は、foreach文を使う方がよりシンプルで読みやすくなることが多いです。

“`csharp
string[] fruits = { “Apple”, “Banana”, “Cherry”, “Date” };

foreach (string fruit in fruits)
{
Console.WriteLine(fruit); // インデックスを使わずに直接要素が取れる
}
“`

しかし、for文はインデックスを直接操作できるため、「特定のインデックスの要素だけ処理したい」「2つ飛ばしで処理したい」「後ろから処理したい」といった、より柔軟な処理を行う場合にforeachよりも適しています。配列の要素を変更したい場合も、インデックスが必要なfor文を使う必要があります。

4.4. 文字列を1文字ずつ処理する

文字列も文字の集まりとして考えることができ、配列のようにインデックスで各文字にアクセスできます。文字列の長さは .Length プロパティで取得できるため、配列と同じようにfor文で処理できます。

“`csharp
string message = “Hello”;

for (int i = 0; i < message.Length; i++)
{
char character = message[i]; // 文字列[インデックス] で文字を取得
Console.WriteLine($”Index {i}: {character}”);
}
// 出力:
// Index 0: H
// Index 1: e
// Index 2: l
// Index 3: l
// Index 4: o
“`

4.5. ネストされたfor文 (多重ループ)

for文のループ本体の中に、別のfor文を書くことができます。これをネスト(入れ子)されたループ、または多重ループと呼びます。これは、九九の表や、2次元配列(行と列がある表形式のデータ)を処理する場合などによく使われます。

外側のループが1回実行されるごとに、内側のループは最後まで実行されます。

例:九九の表の一部を表示 (3の段まで、かける数も3まで)

csharp
// 外側のループ (かけられる数 i)
for (int i = 1; i <= 3; i++)
{
// 内側のループ (かける数 j)
for (int j = 1; j <= 3; j++)
{
// 内側のループの処理
Console.Write($"{i} * {j} = {i * j}\t"); // \tはタブ区切り
}
// 内側のループが終わったら、改行して次の段へ (外側のループの処理)
Console.WriteLine();
}
// 出力:
// 1 * 1 = 1 1 * 2 = 2 1 * 3 = 3
// 2 * 1 = 2 2 * 2 = 4 2 * 3 = 6
// 3 * 1 = 3 3 * 2 = 6 3 * 3 = 9

この実行フローを追ってみましょう:

  1. 外側ループ初期化: i = 1
  2. 外側ループ条件チェック: i <= 3true
  3. 外側ループ本体に入る。
  4. 内側ループ初期化: j = 1 (ここで内側ループが始まる)
  5. 内側ループ条件チェック: j <= 3true
  6. 内側ループ本体実行: 1 * 1 = 1 を表示。
  7. 内側ループ更新式: j2 になる。
  8. 内側ループ条件チェック: j <= 3true
  9. 内側ループ本体実行: 1 * 2 = 2 を表示。
  10. 内側ループ更新式: j3 になる。
  11. 内側ループ条件チェック: j <= 3true
  12. 内側ループ本体実行: 1 * 3 = 3 を表示。
  13. 内側ループ更新式: j4 になる。
  14. 内側ループ条件チェック: j <= 3false内側ループ終了。
  15. 外側ループ本体の残りの処理を実行: Console.WriteLine(); (改行)。
  16. 外側ループ更新式: i2 になる。
  17. 外側ループ条件チェック: i <= 3true
  18. 外側ループ本体に入る。
  19. 内側ループ初期化: j = 1 (再び内側ループが最初から始まる)
  20. 内側ループ条件チェック: j <= 3true
  21. 内側ループ本体実行: 2 * 1 = 2 を表示。
    … (内側ループが j=3 まで実行される) …
  22. 内側ループが終了する (jが4になる)。
  23. 外側ループ本体の残りの処理を実行: 改行。
  24. 外側ループ更新式: i3 になる。
  25. 外側ループ条件チェック: i <= 3true
  26. 外側ループ本体に入る。
  27. 内側ループ初期化: j = 1 (再び内側ループが最初から始まる)
    … (内側ループが j=3 まで実行される) …
  28. 内側ループが終了する (jが4になる)。
  29. 外側ループ本体の残りの処理を実行: 改行。
  30. 外側ループ更新式: i4 になる。
  31. 外側ループ条件チェック: i <= 3false外側ループ終了。

このように、ネストされたループは、外側が1周する間に内側が設定回数だけ繰り返し実行される構造になります。3重、4重とネストすることも可能ですが、コードが複雑になり読みにくくなる傾向があるため、多すぎるネストは避けるのが一般的です。

第5章:ループの流れを制御する – breakcontinue

for文は基本的に条件式がfalseになるまで繰り返しますが、ループの途中で特別な操作を行いたい場合があります。例えば、「リストの中に特定の値が見つかったら、それ以上探す必要はないからループをすぐに止めたい」とか、「特定の条件を満たす要素だけ処理して、それ以外はスキップしたい」といった場合です。

このようなときに役立つのが、break文とcontinue文です。

5.1. break文:ループを即座に終了する

break文は、それが含まれる最も内側のループ(for, while, do-while, foreach)の実行を即座に終了させます。break文が実行されると、その時点でのループの残りの処理はスキップされ、ループの直後のコードに処理が移ります。

主に、

  • 探索処理などで目的の要素が見つかった場合
  • 特定の異常な状態が発生した場合

などに使用されます。

例:配列の中から特定の数字を探す

“`csharp
int[] numbers = { 5, 12, 8, 25, 3, 18, 9 };
int target = 25;
bool found = false;

for (int i = 0; i < numbers.Length; i++)
{
Console.WriteLine($”Checking index {i} (value: {numbers[i]})”);

if (numbers[i] == target)
{
    Console.WriteLine($"Target {target} found at index {i}.");
    found = true;
    break; // 目的の値が見つかったので、ループをここで終了
}

}

if (!found)
{
Console.WriteLine($”Target {target} not found in the array.”);
}
// 出力 (この例の場合):
// Checking index 0 (value: 5)
// Checking index 1 (value: 12)
// Checking index 2 (value: 8)
// Checking index 3 (value: 25)
// Target 25 found at index 3.
// (この後、ループは終了し、”Target 25 not found…” は表示されない)
“`

もしbreak;がなければ、ループは配列の最後まで(インデックス6まで)実行されます。breakを使うことで、無駄な繰り返しを省き、処理の効率を上げることができます。

ネストされたループ内でbreakを使うと、breakを含む最も内側のループだけが終了します。外側のループはそのまま続行されます。

csharp
for (int i = 1; i <= 3; i++)
{
for (int j = 1; j <= 3; j++)
{
Console.WriteLine($"Outer: {i}, Inner: {j}");
if (i == 2 && j == 2)
{
break; // このbreakは内側のjのループだけを終了させる
}
}
Console.WriteLine($"--- Inner loop finished for i={i} ---");
}
// 出力:
// Outer: 1, Inner: 1
// Outer: 1, Inner: 2
// Outer: 1, Inner: 3
// --- Inner loop finished for i=1 ---
// Outer: 2, Inner: 1
// Outer: 2, Inner: 2
// --- Inner loop finished for i=2 ---
// Outer: 3, Inner: 1
// Outer: 3, Inner: 2
// Outer: 3, Inner: 3
// --- Inner loop finished for i=3 ---

外側のループも同時に終了させたい場合は、フラグ変数を使ったり、メソッドごと抜けるなどの工夫が必要です。

5.2. continue文:現在の繰り返しをスキップする

continue文は、それが含まれるループの現在の繰り返し処理を中断し、次の繰り返しに移ります。continue文が実行されると、ループ本体のそれ以降のコードはスキップされ、すぐに更新式が実行され、その後に条件式が評価されます。

主に、

  • 特定の条件を満たす要素だけをスキップしたい場合
  • エラーデータを無視して処理を進めたい場合

などに使用されます。

例:配列の中から偶数だけを表示する(奇数はスキップ)

“`csharp
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

for (int i = 0; i < numbers.Length; i++)
{
// もし奇数ならスキップ
if (numbers[i] % 2 != 0) // % は剰余演算子 (割り算のあまり)
{
Console.WriteLine($”Skipping odd number: {numbers[i]}”);
continue; // 次の繰り返しへスキップ
}

// ここから下のコードは偶数の場合のみ実行される
Console.WriteLine($"Processing even number: {numbers[i]}");

}
// 出力:
// Skipping odd number: 1
// Processing even number: 2
// Skipping odd number: 3
// Processing even number: 4
// Skipping odd number: 5
// Processing even number: 6
// Skipping odd number: 7
// Processing even number: 8
// Skipping odd number: 9
// Processing even number: 10
“`

continue; が実行されると、その回のループ本体の残りの部分(この例では Console.WriteLine("Processing even number: ...");)は実行されずにスキップされ、すぐにi++が実行されて次のインデックスの処理に移ります。

continuebreakと同様、最も内側のループに作用します。

第6章:for文の落とし穴と注意点

for文は強力で便利ですが、使い方を間違えると意図しない動作になったり、エラーが発生したりします。よくある落とし穴とその対策を知っておきましょう。

6.1. オフ・バイ・ワン エラー (Off-by-one Error)

これは、ループの繰り返し回数が1回多かったり少なかったりする、プログラマが非常によく遭遇するエラーです。特に、条件式での <<= の使い分けや、インデックスの開始値・終了値に関わる部分で発生しやすいです。

例:0から4までを表示したいのに、5まで表示されてしまう

csharp
// ダメな例 (1回多い)
for (int i = 0; i <= 5; i++) // 条件式が i <= 5 になっている
{
Console.WriteLine(i);
}
// 出力: 0, 1, 2, 3, 4, 5 (6回実行されている)

例:1から5までを表示したいのに、4までしか表示されない

csharp
// ダメな例 (1回少ない)
for (int i = 1; i < 5; i++) // 条件式が i < 5 になっている
{
Console.WriteLine(i);
}
// 出力: 1, 2, 3, 4 (4回実行されている)

対策:

  • 「開始値」と「終了値」そして「条件演算子」の組み合わせを明確にする。
    • 「0からN-1まで」繰り返したい場合(配列のインデックスなど、N回繰り返したい場合):for (int i = 0; i < N; i++) が典型的です。iは0から始まり、N-1まで処理し、Nになったら < Nfalseになり終了します。繰り返し回数は N 回です。
    • 「1からNまで」繰り返したい場合:for (int i = 1; i <= N; i++) が典型的です。iは1から始まり、Nまで処理し、N+1になったら<= Nfalseになり終了します。繰り返し回数は N 回です。
  • 具体的な小さい数値で手動で実行フローを追ってみる。 例えば、N=5で考えたときに、iがどう変化し、条件式がどの値でfalseになるかを確認します。
  • 配列の処理では、i < array.Length を常に意識する。 インデックスは0からLength - 1までなので、<= array.Length - 1 と書くことも可能ですが、i < array.Length の方がより簡潔で一般的です。

6.2. 無限ループ (Infinite Loop)

条件式がいつまでたってもfalseにならない場合に発生します。プログラムが停止せず、CPUリソースを大量に消費したり、フリーズしたりする原因となります。

例:条件が常にtrueになる

csharp
// ダメな例 (無限ループ)
for (int i = 0; i < 10; i--) // 初期値 i=0, 条件 i < 10, 更新 i-- (減らす)
{
Console.WriteLine(i); // iは 0, -1, -2... と減っていくため、i < 10 は常にtrueになる
}

例:更新式がない、または条件式に関係ない

csharp
// ダメな例 (無限ループ)
int i = 0; // ループの外で宣言・初期化
for (; i < 10; ) // 初期化式も更新式も省略
{
Console.WriteLine(i); // iの値が変わらないため、i < 10 は常にtrueになる
// i++; // これがないと無限ループ
}

対策:

  • 条件式が最終的にfalseになるように、更新式でループ変数を適切に変更する。
  • 条件式がループ変数に依存しているか確認する。
  • 更新式が正しくループ変数を目標の値に近づけているか確認する。 (増えるべきなのに減らしていないか、など)
  • デバッガを使って、ループ変数の値がどのように変化しているかステップ実行で確認する。

意図的に無限ループを作ることもありますが(例:サービスプロセスのように常駐して処理を待ち受ける場合)、その場合はループ内に適切な終了条件(例えば、特定のフラグが立つ、外部からの停止信号を受け取るなど)を別途設ける必要があります。

6.3. ループ変数をループ内で変更する

ループの更新式(i++など)とは別に、ループ本体の中でループカウンタ変数の値を変更することは、コードの可読性を著しく下げ、予期せぬ動作を引き起こす可能性があるため、避けるべきです。

csharp
// 推奨されない例
for (int i = 0; i < 10; i++)
{
Console.WriteLine(i);
if (i == 5)
{
i = 8; // ループ変数iをここで変更
Console.WriteLine("i changed to 8");
}
}
// 出力:
// 0
// 1
// 2
// 3
// 4
// 5
// i changed to 8
// 9 (iが8になった後、更新式で9になり、条件i<10はtrueなのでループ本体へ)
// (ループはここで終了。iが9になった後更新式で10になり、条件i<10がfalseになる)

この例では、iが5のときに強制的に8に変更されるため、本来実行されるはずだった6、7、8の回の処理がスキップされます。このようなコードは、なぜこのような挙動をするのか理解するのが難しく、デバッグも困難になります。

対策:

  • ループカウンタ変数は、基本的に初期化式と更新式、そして条件式でのみ使用し、ループ本体内で値を変更しない。
  • ループの進行をスキップしたい場合はcontinue、ループを終了したい場合はbreakを使用する。
  • どうしてもループ変数をループ内で調整したい複雑なケースでは、そのロジックを丁寧にコメントで説明するか、while文などfor文以外のループを検討する。

6.4. 浮動小数点数でのループ制御

doublefloatといった浮動小数点数型をループカウンタ変数として使い、厳密な等価比較 (==) を条件式に使うのは危険です。浮動小数点数はコンピュータ内部で正確に表現できない場合があるため、期待した値にぴったり一致せず、ループが終了しなかったり、意図しない回数実行されたりする可能性があります。

csharp
// ダメな例 (浮動小数点数での等価比較)
for (double d = 0.1; d != 1.0; d += 0.1) // dが1.0に正確に一致しない可能性がある
{
Console.WriteLine(d);
}

対策:

  • 可能な限り、ループカウンタ変数には整数型 (int, longなど) を使用する。
  • どうしても浮動小数点数でループを制御したい場合は、条件式を範囲での比較にする (<, <=, >, >=) か、あるいは整数型のカウンタ変数を使用し、ループ本体内で浮動小数点数に変換して計算する。

例:整数型カウンタで0.1刻みの処理を行う

csharp
for (int i = 1; i <= 10; i++)
{
double d = i * 0.1;
Console.WriteLine(d);
}
// 出力: 0.1, 0.2, ..., 1.0

6.5. 複雑すぎる式をループ制御部分に書く

初期化式、条件式、更新式には任意の式を書くことができますが、あまりに複雑な処理をここに詰め込むと、for文の役割(どのように繰り返されるか)が分かりにくくなり、可読性が低下します。

csharp
// 推奨されない例 (複雑すぎる)
for (int i = CalculateStart(); i < GetThreshold(i) && !IsFinished(); i = UpdateCounter(i, someParam))
{
// ... 処理 ...
}

対策:

  • ループの制御に関わる部分は、できるだけシンプルに保つ。 変数の宣言、簡単な比較、単純な増減など。
  • 複雑な計算や条件チェックが必要な場合は、それらをループ本体の中のif文などに記述する。
  • 関数呼び出しをループ制御部分に使う場合は、その関数が何をしているのか(特に副作用がないか)を明確にする。

for文の丸カッコ内の3つの部分は、そのループが「いつ始まり」「いつ終わり」「どう進行するか」という最も重要な情報を含んでいます。ここがシンプルであるほど、コードを読んだ人がループの挙動を素早く理解できます。

第7章:実践的なfor文の活用例

これまでに学んだfor文の知識を使って、いくつか実践的な問題を解いてみましょう。

7.1. 数字の合計値を計算する

ある範囲の数字の合計を計算するのは、for文の典型的な使い道の一つです。

例:1から100までの整数の合計値を求める

“`csharp
int sum = 0; // 合計値を格納する変数。ループの前に0で初期化する。

// 1から100まで繰り返す
for (int i = 1; i <= 100; i++)
{
sum = sum + i; // または sum += i;
}

Console.WriteLine($”1から100までの合計は: {sum}”);
// 出力: 1から100までの合計は: 5050
“`

この例では、sumという変数をループの外側で宣言・初期化しているのがポイントです。ループで宣言すると、ループが1回終わるごとに変数が消滅してしまい、合計値を保持できません。ループ内で計算した結果をループの繰り返しを超えて保持したい場合は、このようにループの外側で変数を宣言する必要があります。

7.2. 配列の最大値や最小値を求める

配列の中に入っている数値の中で、最も大きい値(最大値)や最も小さい値(最小値)を探す処理も、for文を使って実現できます。

例:配列 numbers の最大値を求める

“`csharp
int[] numbers = { 45, 12, 78, 34, 99, 21, 56 };
int maxValue = numbers[0]; // 仮の最大値として、配列の最初の要素をセットしておく

// 2番目の要素 (インデックス1) から最後までをチェック
for (int i = 1; i < numbers.Length; i++)
{
if (numbers[i] > maxValue)
{
maxValue = numbers[i]; // 現在の最大値より大きければ更新
}
}

Console.WriteLine($”配列の最大値は: {maxValue}”);
// 出力: 配列の最大値は: 99
“`

最小値を求める場合は、初期値を最初の要素にし、条件式を numbers[i] < minValue に変えればOKです。

初期値を配列の最初の要素ではなく、非常に小さい値(最大値の場合)や非常に大きい値(最小値の場合)に設定するという方法もありますが、配列が空だった場合や、値の範囲が不明確な場合は、最初の要素を初期値とする方が安全です。

7.3. 文字列を逆順にする

文字列をfor文を使って逆順に並べ替えた新しい文字列を作る例です。

“`csharp
string original = “Programming”;
string reversed = “”; // 逆順にした文字列を格納する変数

// 元の文字列の後ろから前へ (インデックス Length-1 から 0 まで)
for (int i = original.Length – 1; i >= 0; i–)
{
// 1文字ずつ reversed の末尾に追加
reversed += original[i]; // または reversed = reversed + original[i];
}

Console.WriteLine($”元の文字列: {original}”);
Console.WriteLine($”逆順にした文字列: {reversed}”);
// 出力:
// 元の文字列: Programming
// 逆順にした文字列: gnimmargorP
“`

  • 初期化式: i = original.Length - 1; (文字列の最後のインデックスから開始)
  • 条件式: i >= 0; (インデックス0まで処理する)
  • 更新式: i--; (インデックスを1ずつ減らす)

このように、文字列の処理でもインデックスとfor文が非常に役立ちます。

7.4. パターンを出力する(アスタリスクの図形など)

ネストされたfor文を使って、アスタリスク (*) や他の文字で様々な図形(四角、三角形など)を描画するのも、for文の練習によく使われます。

例:5×5の四角形をアスタリスクで描画

“`csharp
int size = 5;

// 外側ループ: 行 (row) を制御
for (int row = 0; row < size; row++)
{
// 内側ループ: 列 (column) を制御
for (int col = 0; col < size; col++)
{
Console.Write(““); // アスタリスクを表示 (改行しない)
}
// 内側ループが終わったら、改行して次の行へ
Console.WriteLine();
}
// 出力:
//
*
//

//

//

//
***
“`

例:直角三角形を描画

“`csharp
int size = 5;

for (int row = 0; row < size; row++)
{
// 列の数は行番号+1だけ増える (0行目は1個, 1行目は2個…)
for (int col = 0; col <= row; col++) // 条件式が col <= row に注目
{
Console.Write(““);
}
Console.WriteLine();
}
// 出力:
// *
// **
// ***
//

//
**
“`

内側のループの条件式を col <= row とすることで、外側のループ変数 (row) に応じて内側のループの繰り返し回数(表示するアスタリスクの数)が変化しています。このように、ネストされたループの条件式や本体の処理で、外側のループ変数を使うことで、様々なパターンを生成できます。

第8章:for文と他のループの使い分け

C#にはfor文の他にもwhile文やdo-while文があります。それぞれに得意な状況があります。for文がどのような場合に特に適しているのかを再確認し、他のループとの使い分けのポイントを理解しましょう。

for文が適している場合

  • 繰り返しの回数が事前にわかっている、あるいは計算できる場合。 (例: 10回繰り返す, 配列の要素数だけ繰り返す)
  • 特定の範囲のインデックスや数値を使って順序どおりに処理を進めたい場合。 (例: 配列の0番目から順に処理する, 1から100まで数えながら処理する)
  • ループカウンタ変数を初期化し、条件によって継続判定し、決まったパターンで変数を更新する、という制御構造が明確な場合。 for文の構文が、これらの要素をまとめて記述できるため、コードがすっきりします。

while文が適している場合

csharp
while (条件式)
{
// 繰り返したい処理
}

  • 繰り返し回数が事前に確定しておらず、ある条件が満たされるまで(あるいは満たされなくなるまで)繰り返したい場合。 (例: ファイルの最後まで読み込むまで繰り返す, ユーザーが特定の入力をするまで繰り返す, キューに要素がある間繰り返す)
  • ループの進行が、単純なカウントアップ/ダウンではない複雑なロジックに依存している場合。 while文は条件式だけが必須なので、ループ本体の中で複雑な計算や状態変化を行い、それが条件式に影響を与えるような場合に柔軟に対応できます。

例:ユーザーが “quit” と入力するまで入力を受け付ける

csharp
string input = "";
while (input != "quit")
{
Console.Write("何か入力してください ('quit'で終了): ");
input = Console.ReadLine();
if (input != "quit")
{
Console.WriteLine($"入力されました: {input}");
}
}
Console.WriteLine("終了します。");

この例では、ループが何回繰り返されるかは事前に分かりません。ユーザーの入力に依存します。このような場合はwhile文が自然です。もちろんfor (;;) という無限ループの構文とifbreakを組み合わせて同様のことができますが、while文の方が「条件が真の間繰り返す」という意図が明確に伝わります。

do-while文が適している場合

csharp
do
{
// 繰り返したい処理 (最低1回は必ず実行される)
} while (条件式);

  • ループ本体の処理を、まず最低1回は実行し、その後に条件を判定して繰り返すかどうかを決めたい場合。 (例: ユーザーに何か入力を促し、その入力が特定の条件を満たすまで繰り返す)

例:有効な整数が入力されるまで繰り返す

“`csharp
int number;
bool isValidInput;

do
{
Console.Write(“整数を入力してください: “);
string input = Console.ReadLine();
// int.TryParse() は変換できたらtrue、できなければfalseを返す
isValidInput = int.TryParse(input, out number);

if (!isValidInput)
{
    Console.WriteLine("無効な入力です。整数を入力してください。");
}

} while (!isValidInput); // 入力が無効な間(!isValidInput が true)繰り返す

Console.WriteLine($”有効な整数が入力されました: {number}”);
“`

この例では、ユーザーに必ず一度は入力を促す必要があります。そのため、最初にループ本体を実行してから条件 (!isValidInput) をチェックするdo-whileが適しています。

foreach文が適している場合

csharp
foreach (型 変数名 in コレクション)
{
// コレクションの各要素を使った処理
}

  • 配列やリストなどのコレクションの各要素に対して、順番に処理を行いたい場合で、かつ要素のインデックス自体は必要ない場合。

例:リストの各要素を表示する

“`csharp
List names = new List { “Alice”, “Bob”, “Charlie” };

foreach (string name in names)
{
Console.WriteLine($”Name: {name}”);
}
“`

foreachは最もシンプルにコレクションを反復処理できるため、インデックスが不要な場合はfor文よりも優先的に検討されることが多いです。ただし、コレクションの要素をループ内で変更する(追加、削除など)処理を行う場合は、for文でインデックスを管理しながら行う方が安全な場合があります(foreach中にコレクションを変更するとエラーになることがあるため)。

まとめると:

  • for文: 回数が明確、またはインデックスを使った順次処理。
  • while文: 条件が満たされるまで繰り返す(回数不定)。
  • do-while文: 最低1回は実行し、条件が満たされるまで繰り返す(回数不定)。
  • foreach文: コレクションの全要素をインデックスなしで順に処理。

これらのループを適切に使い分けることで、より意図が明確で保守しやすいコードを書くことができます。

第9章:for文をより効果的に使うためのヒント

最後に、for文を使う上での役立つヒントをいくつかご紹介します。

9.1. 適切なループ変数名を選ぶ

簡単なカウントアップのような場合はi, j, kといった変数が慣習的によく使われますが、配列のインデックスや特定の意味を持つループの場合は、より分かりやすい名前を選ぶとコードが読みやすくなります。

例:
* index または idx (配列やリストのインデックス)
* count (何かの数を数える)
* row, col (行列処理、ネストされたループ)
* year, month, day (日付のループ)

9.2. 変数のスコープを理解する

for文の初期化式で宣言した変数(例: int i = 0;i)は、そのfor文のブロック {} の中でだけ有効です。ループが終わるとその変数は消滅し、外からはアクセスできません。

csharp
for (int i = 0; i < 5; i++)
{
Console.WriteLine(i); // OK: iはこのスコープ内で有効
}
// Console.WriteLine(i); // NG: ここでは変数iは存在しない

もし、ループの最後にループカウンタがどの値で終わったかを知りたい場合など、ループ終了後も変数を使いたい場合は、ループの外側で変数を宣言しておく必要があります。

csharp
int i; // ループの外側で宣言
for (i = 0; i < 5; i++) // 初期化式では宣言しない
{
Console.WriteLine(i);
}
Console.WriteLine($"ループ終了時のiの値: {i}"); // OK: iはこのスコープでも有効
// 出力:
// 0
// 1
// 2
// 3
// 4
// ループ終了時のiの値: 5

9.3. 式の省略

for文の丸カッコ内の初期化式、条件式、更新式は、それぞれ省略することが可能です。

  • 初期化式を省略: ループ変数などがすでに定義され、初期化も済んでいる場合。
    例:int i = 0; for (; i < 10; i++) { ... }
  • 条件式を省略: 条件式を省略すると、常にtrueと評価されます。これは無限ループを作成する一般的な方法です。ループを終了するには、ループ本体内でbreak文を使う必要があります。
    例:for (int i = 0; ; i++) { if (i >= 10) break; ... } または for (;;) { ... break; }
  • 更新式を省略: ループ変数の更新がループ本体の中で行われる場合など。
    例:for (int i = 0; i < 10;) { ... i++; }

すべての式を省略した for (;;) は、完全に条件に依存しない無限ループとなり、break文でのみ終了させることができます。

これらの省略記法はコードを簡潔にすることができますが、ループの挙動が一見して分かりにくくなる可能性もあります。特に初心者のうちは、明示的に初期化式、条件式、更新式をすべて記述する方が、間違いを防ぎやすく、コードの意図も伝わりやすいため推奨されます。

9.4. パフォーマンスに関する考慮事項

ほとんどの場合、for文の実行速度は非常に高速であり、パフォーマンスが問題になることは稀です。しかし、非常に巨大なデータを扱う場合や、ループ本体内の処理が非常に重い場合は、パフォーマンスも考慮に入れる必要があります。

  • ループ内で不要なオブジェクト生成を避ける: 繰り返しごとに新しいオブジェクトを生成するような処理は、ループの外に出せないか検討します。
  • ループの繰り返し回数を最小限にする: 同じ結果が得られるなら、より少ない回数で済むアルゴリズムを検討します。
  • 配列の境界チェック: C#では配列アクセス時に自動的に境界チェックが行われますが、これがオーバーヘッドになるごく稀なケースでは、unsafeコードやSpanのような低レベルな最適化手法を検討することもあります(ただし、これは高度なトピックです)。

ただし、まずは「正しく動く」「読みやすい」コードを書くことを優先し、パフォーマンス最適化はボトルネックになっていることが特定されてから行うのが良いアプローチです。

まとめ:for文を自信を持って使おう!

この記事では、C#のfor文について、その基本的な構文、実行フロー、様々な使い方、breakcontinueによる制御、よくある落とし穴、実践的な応用例、そして他のループとの使い分けまで、非常に詳しく解説しました。

for文は、

  • 「〇回繰り返す」
  • 「〇から〇まで、〇ずつ増減させながら処理する」
  • 「配列や文字列の要素をインデックスを使って順番に処理する」

といった、繰り返しの処理を記述する上で非常に強力で基本的なツールです。

最初は丸カッコの中の3つの式が難しく感じるかもしれませんが、それぞれの役割(初期化、継続条件、次へのステップ)を理解し、特に条件式の評価と更新式の実行タイミングを把握すれば、どんな繰り返し処理でもfor文で表現できるようになります。

練習が何より大切です。 ぜひこの記事で学んだことを活かして、色々な繰り返し処理を自分で書いてみてください。

  • 1から10までの偶数だけ表示する
  • 配列の中から50以上の数だけを数える
  • 入力された文字列に特定の文字がいくつ含まれているか数える
  • 九九の表全体を表示する
  • アスタリスクで逆三角形やひし形を描いてみる

など、簡単なものから始めて、徐々に複雑な問題に挑戦してみましょう。

もし途中で分からなくなったり、エラーが出たりしても大丈夫です。この記事を読み返したり、エラーメッセージを検索したり、小さい例で試したりしながら、一歩ずつ進んでいきましょう。

for文をマスターすれば、あなたのプログラミングスキルは格段に向上するはずです。「もう困らない!」という自信を持って、C#での開発を楽しんでください!

これで、C#のfor文に関する詳細な解説記事は終わりです。お役に立てれば幸いです。

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール