C#

【C#】制御文②:繰り返し処理~繰り返しステートメント~

C#の制御文は条件分岐を行う選択ステートメント、繰り返し処理を行う繰り返しステートメント、制御を移動するジャンプステートメントがあります。この記事ではC#の繰り返し処理を行う繰り返しステートメントについて、簡単なコードで解説します。C#の選択ステートメントには、 for文、foreach文、while文、do while文 があります。

繰り返し処理とは

繰り返し処理とは、決まった回数や条件を満たすまで、同じ処理を繰り返し行う場合に使います。ループ処理とも呼ばれます。例えば、下記のコードを見てみましょう。

コード

using System;

internal class Program
{
    static void Main(string[] args)
    {
        int[] n = new int[3];

        Console.Write("整数を入力してください:");
        n[0] = int.Parse(Console.ReadLine());

        Console.Write("整数を入力してください:");
        n[1] = int.Parse(Console.ReadLine());

        Console.Write("整数を入力してください:");
        n[2] = int.Parse(Console.ReadLine());

        Console.WriteLine("3つの整数の合計は{0}です", n[0] + n[1] + n[2]);
    }
}

出力結果

整数を入力してください:3
整数を入力してください:4
整数を入力してください:5
3つの整数の合計は12です

このコードの9~10行目は、整数の入力をユーザーに促し、int型の配列nにユーザーが入力した整数を入れています。しかし12-13行目でも15-16行目でも同じ処理を繰り返しています。このような処理は例えばfor文を用いて下記のように記述できます。for文については後でわかりやすく説明します。

コード

using System;

internal class Program
{
    static void Main(string[] args)
    {
        int[] n = new int[3];

        for(int i=0;i<3;i++)
        {
            Console.Write("整数を入力してください:");
            n[i] = int.Parse(Console.ReadLine());
        }

        Console.WriteLine("3つの整数の合計は{0}です", n[0] + n[1] + n[2]);
    }
}

このようにfor文を使えば、短い記述で表現できます。もし100回の繰り返しをfor文などを使わずに書くと大変ですよね。
繰り返しの使いどころとしては、下記のような場合があります。

  • 特定の回数だけ同じ処理を行う場合
  • ある条件になるまで、同じ処理を繰り返す場合
  • 配列などのコレクションの要素すべてに対して同じ処理を行う場合

for文

ここからは具体的な構文について説明していきます。まずはfor文です。for文はforループとも呼ばれます。C#のfor文は自由度が高いので、覚えるととても役に立ちます。後で説明するwhile文とかdo while文はいらないんじゃないか?と思ってしまうほどです。

for文の基本

では、まずはfor文の構文を説明します。

for(初期化子; 条件; 反復子)
{
  繰り返し処理
}

です。繰り返し行う処理が、1行の文の場合には下記のように{}を省略して書くこともできます。

for(初期化子; 条件; 反復子 ) 文;

一般的な使い方で説明します。次のコードは配列に値を代入する例です。初期化子セクションはint i=0、条件セクションはi<10、反復子セクションはi++となっています。

コード

using System;

internal class Program
{
    static void Main(string[] args)
    {
        int[] n = new int[10];

        for (int i = 0; i < 10; i++)
        {
            n[i] = i ;
        }

        Console.WriteLine("{0}=[{1}]", nameof(n), string.Join(" ", n));
    }
}

出力結果

n=[0 1 2 3 4 5 6 7 8 9]

初期化子セクション

for文内で使用される変数の初期化をします。この初期化はループに入る前に1回だけ行われます。上記の例では変数iを宣言し値0で初期化しています。ここで宣言された変数はfor文の中だけで有効です。外部で宣言されている値を使用することもできます。以下のようにfor文の前で宣言されている変数を使用することもできます。

int i;        
for (i = 0; i < 10; i++)
{
       n[i] = i * (i + 1);
}

条件セクション

for文を継続する条件を指定します。ここではi<10が指定されています。したがって、i<10の条件が満たされている間はforループを継続します。

反復子セクション

この箇所には、1回のforループが終わったときにする処理を書きます。上記の例ではi++となっています。すなわち1回のループでiが+1されます。

以上の内容をまとめると、上記のコードは、「for文をi=0で開始し、毎ループごとにiを+1加算し、i<10である間、繰り返し処理を行う」という内容です。ここでいう「繰り返し処理」とは配列n[i]にiを代入するというものです。

このようにfor文では初期化子セクションでint型のカウンター変数を宣言および初期化し、条件セクションでカウンター変数を使ってループ回数の条件を決め、反復子セクションでカウンター変数を+1していく、というのがオーソドックスな使い方です。

for文の応用

基本は前項のとおりなのですが、C#のfor文は驚くほど柔軟性があります。初期化子セクション、条件セクション、反復子セクションの3つの項目の柔軟性について解説しますが、その前にすべてを省略した場合について説明します。

すべてを省略できる

for文は初期化子、条件、反復子をすべて空欄にできます。その場合、初期化は行われず、条件判定もされず、反復するものはありません。コードは以下のようになります。

コード

using System;

internal class Program
{
    static void Main(string[] args)
    {
        Random rand = new Random();

        for (; ; )
        {
            int val = rand.Next(-10, 11);
            Console.WriteLine("数値は{0}です", val);
            if (val > 0) break;
        }
    }
}

出力結果

数値は-3です
数値は-2です
数値は-2です
数値は2です

上記コードの9行目のfor文は初期化子、条件、反復子がすべて空欄です。このような状態だと無限ループになりますので、何らかの条件でfor文から抜ける必要があります。for文から抜けるには、13行目のようにbreak文を使います。この例では-10~10までの乱数を生成してそれが10より大きければbreak文でforループを抜けています。乱数を使っているので実行結果は毎回異なります。

なお、下の2つの文は同じ処理をします。初期化子はfor文の前、条件はfor文内の最初にif文とbreak文として(!があることに注意)、反復子はfor文内の最後に書くことで同じ処理ができます。

int i;
for (i=0; i < 10; i++)
{
  Console.WriteLine(“i={0}”, i);
}

int i = 0;
for (; ; )
{
  if (!(i < 10)) break;
  Console.WriteLine(“i={0}”, i);
  i++;
}

    

初期化セクション

初期化子セクションでは、for文内で使用する変数の初期化を行いますが、int型のカウンター変数に限らずfor文内で使用するすべての変数を初期化できます。

複数の変数を宣言、初期化する

コード

using System;

internal class Program
{
    static void Main(string[] args)
    {
        for (double a = 0, b = 0, c = 0; a < 10; a++)
        {
            b++;
            c++;
            a = b + c;
            Console.WriteLine("a={0},b={1},c={2}", a, b, c);
        }
    }
}

出力結果

a=2,b=1,c=1
a=4,b=2,c=2
a=6,b=3,c=3
a=8,b=4,c=4
a=10,b=5,c=5

上記コードでは初期化子は、

double a = 0, b = 0, c = 0

です。このように,(カンマ)で区切ることで複数の変数を宣言と初期化できます。ただし、宣言できる型はひとつです。ここではdouble型の変数a,b,cを宣言して初期化しています。

型の異なる複数の変数の初期化

型の異なる複数の変数を初期化したい場合には、for文の前に変数を宣言しておく必要があります。for文の初期化セクションでは複数の型を宣言できません。

コード

using System;

internal class Program
{
    static void Main(string[] args)
    {
        double a;
        int b;
        int c;

        for (a = 0, b = 0, c = 0; a < 10; a++)
        {
            b++;
            c++;
            a = b + c;
            Console.WriteLine("a={0},b={1},c={2}", a, b, c);
        }
    }
}

出力結果

a=2,b=1,c=1
a=4,b=2,c=2
a=6,b=3,c=3
a=8,b=4,c=4
a=10,b=5,c=5

上記コードでは、7~9行目でfor文で使う変数を宣言しておき、11行目のfor文の初期化子セクションで初期化しています。上記コードと下記コードは同じ動作をします。下記コードではfor文の前に変数を宣言、初期化し、for文の初期化子セクションは空欄にしています。

コード

using System;

internal class Program
{
    static void Main(string[] args)
    {
        double a = 0;
        int b = 0;
        int c = 0;

        for (; a < 10; a++)
        {
            b++;
            c++;
            a = b + c;
            Console.WriteLine("a={0},b={1},c={2}", a, b, c);
        }
    }
}

出力結果

a=2,b=1,c=1
a=4,b=2,c=2
a=6,b=3,c=3
a=8,b=4,c=4
a=10,b=5,c=5

条件セクション

条件セクションにはbool型の値が入りますが、trueであればforループを継続、falseであれば終了します。複合条件でも構いませんし、true,falseを直接入れることだってできます。

下記は複合条件を用いた例です。

コード

using System;

internal class Program
{
    static void Main(string[] args)
    {
        double d = 0;
        for (double i = 1; d < 1 && i < 5; i++)
        {
            d = i / (i + 1);
            Console.WriteLine("d={0:0.00000000}", d);
        }
    }
}

出力結果

d=0.50000000
d=0.66666667
d=0.75000000
d=0.80000000

反復子セクション

反復子セクションも,(カンマ)で区切ることで複数のステートメントを記述できます。記述できるステートメントは、インクリメント、デクリメント、代入、メソッドの呼び出し、new演算子を使用したオブジェクトの生成などです。
次の例では反復子iをインクリメント、反復子jを代入によりj=i*jとして増加させています。さらにiとjの値をConsole.Writelineメソッドで表示させています。

コード

using System;

internal class Program
{
    static void Main(string[] args)
    {
        for (int i = 0, j = 1; i < 5; i++, j = i * j, Console.Write("i={0},j={1}のとき ", i, j))
        {
            int ans = i + j;
            Console.WriteLine("ans={0}", ans);
        }
    }
}

出力結果

ans=1
i=1,j=1のとき ans=2
i=2,j=2のとき ans=4
i=3,j=6のとき ans=9
i=4,j=24のとき ans=28
i=5,j=120のとき

上記コードの例の出力結果からわかるように、forループの最初では反復子セクションは呼び出されずないため、Console.Write()メソッドは呼び出されていません。また、最後では反復子セクションは呼び出されますが、条件セクションでfalseになり、for文の{}内の処理は行われていません。

foreach文

foreach文はfor文と同じように繰り返し処理を行いますが、配列やListやDictionaryなどのコレクションの要素に対して繰り返しを行います。

foreach文の構文は下記のようになります。

foreach(型名 変数名 in コレクション)
{
  繰り返し処理
}

これだけではわかりにくいので、実際に簡単なコードで見ていきましょう。

コード

using System;
using System.Collections.Generic;

internal class Program
{
    static void Main(string[] args)
    {
        List<int> lst = new List<int> { 1, 2, 3, 4, 5 };
        foreach(int i in lst)
        {
            Console.WriteLine("i={0}", i);
        }
    }
}

出力結果

i=1
i=2
i=3
i=4
i=5

上記コードでは、まず8行目でリストオブジェクトlstを初期化しています。9~12行目のforeach文により、lstの要素を表示しています。

foreach文の意味は、lst(コレクション)の各要素をint型の変数iに入れて、{}内の処理を行う、ことを表しています。「 lst(コレクション)の各要素をint型の変数iに入れて」ということを、foreach(int i in lst)で表しています。

多くの場合、型名はvarとすることができます。これはコレクションの要素の型により型推論ができるからです。上記コードの場合には、foreach(var i in lst)としても同じ動作をします。lstの要素がint型であることは明示しなくてもわかるためです。

foreahc文が特に有効なのはDictionaryクラスを利用するときです。Dictionaryクラスには、keyとValueの組み合わせがあるだけで、Listや配列のようにインデックスがないので、for文で直接値を取り出すことはできません。下記コードに例を示します。

コード

using System;
using System.Collections.Generic;

internal class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, double> dict = new Dictionary<string, double> {
                        { "Japan", 1.258 }, { "USA", 3.295 },
                        { "China", 14.02 }, { "India", 13.8 },
                        { "Brazil", 2.126 } };
        foreach (var (key, val) in dict)
        {
            Console.WriteLine("{0}の人口は{1}億人です", key, val);
        }
    }
}

出力結果

Japanの人口は1.258億人です
USAの人口は3.295億人です
Chinaの人口は14.02億人です
Indiaの人口は13.8億人です
Brazilの人口は2.126億人です

上記コードでは、DictionaryオブジェクトdictのkeyとValueの値を、(key,val)というタプルで受け取っています。

上記のコードはタプルを使用せずに下記のように書くこともできます。結果は同じです。

コード

using System;
using System.Collections.Generic;

internal class Program
{
    static void Main(string[] args)
    {
        Dictionary<string, double> dict = new Dictionary<string, double> {
                        { "Japan", 1.258 }, { "USA", 3.295 },
                        { "China", 14.02 }, { "India", 13.8 },
                        { "Brazil", 2.126 } };
        foreach (KeyValuePair<string, double> d in dict)
        {
            Console.WriteLine("{0}の人口は{1}億人です", d.Key, d.Value);
        }
    }
}

上記コードはKeyValuePair<string, double>の箇所をvarに置き換えて型推論を用いても同じ動作です。

while文

while文は、ある条件がtrueとなるまで繰り返し処理をする場合に使用します。構文は以下の通りです。

while(条件式)
{
  繰り返し処理
}

条件式がtrueの間は繰り返し処理が行われます。注意点としてはfor文のように繰り返し回数で処理が終わらないため、条件式がfalseにならないと無限ループに陥ります。下記コードは簡単な例です。

コード

using System;

internal class Program
{
    static void Main(string[] args)
    {
        int i = 0;
        while (i < 3)
        {
            Console.WriteLine("iの値は{0}です", i);
            i++;
        }
    }
}

出力結果

iの値は0です
iの値は1です
iの値は2です

上記のコードでi<5である間は繰り返し処理が行われます。ここでi++を書き忘れるとi=0のままなので無限ループに陥ります。

あえて無限ループにして、break文で抜けることもできます。下記に例を示します。

コード

using System;

internal class Program
{
    static void Main(string[] args)
    {
        int i = 0;
        while (true)
        {
            Console.WriteLine("iの値は{0}です", i);
            if (i == 2) break;
            i++;
        }
    }
}

出力結果

iの値は0です
iの値は1です
iの値は2です

do while文

do while文もwhile文と同様にある条件がtrueとなるまで繰り返し処理をする場合に使用します。while文との違いは、繰り返し処理の最初の1回は必ず実行されます。構文は以下のとおりです。

do
{
繰り返し処理
}while(条件式);

while(条件式)の後に;を忘れがちなので気をつけましょう。while(条件式)が一番最後にあるのは条件判定が最後に行われるためです。このため、最初の1回は必ず実行されるというわkです。

下記にコードの例を示します。

コード

using System;

internal class Program
{
    static void Main(string[] args)
    {
        int i = 0;
        do
        {
            Console.WriteLine("iの値は{0}です", i);
            i++;
        } while (i < 3);
    }
}

出力結果

iの値は0です
iの値は1です
iの値は2です

次に最初の一回が処理されることを確認してみましょう。

コード

using System;

internal class Program
{
    static void Main(string[] args)
    {
        int i = 0;
        do
        {
            Console.WriteLine("iの値は{0}です", i);
            i++;
        } while (false);
    }
}

出力結果

iの値は0です

また、例は省略しますがfor文やwhile文と同じようにbreak文でも繰り返し処理から抜けることができます。

終わりに

ここではC#の選択ステートメントのfor文、foreach文、while文、do while文について簡単なコードを用いて解説しました。特にfor文は万能なので使い方をしっかり覚えてしまいましょう。
他の制御文については、下記もご覧ください。

【C#】制御文①:条件分岐~選択ステートメント~
【C#】制御文③:ジャンプステートメント

ABOUT ME
しかすけ
日々是好日。何とか何とか生きてます。

COMMENT

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