C# 初級講座
VB2019 Visual Studio 2022

第20回 for によるループの基本

2022/10/2

この記事が対象とする製品・バージョン

Visual Studio 2022 Visual Studio 2022 対象です。
Visual Studio 2019 Visual Studio 2019 対象です。
Visual Studio 2017 Visual Studio 2017 対象外ですが、参考になります。
Visual Studio 2015 Visual Studio 2015 対象外ですが、参考になります。
Visual Studio 2013 Visual Studio 2013 対象外ですが、参考になります。
Visual Studio 2012 Visual Studio 2012 対象外ですが、参考になります。
Visual Studio 2010 Visual Studio 2010 対象外ですが、参考になります。
Visual Studio 2008 Visual Studio 2008 対象外ですが、参考になります。
Visual Studio 2005 Visual Studio 2005 対象外ですが、参考になります。
Visual Studio.NET 2003 Visual Studio.NET 2003 × 対象外です。
Visual Studio.NET 2002 Visual Studio.NET (2002) × 対象外です。
  Visual Studio Code 対象外ですが、参考になります。

 

目次

forのいろいろな実例はこちらのサンプルでも公開しています。

for による回数指定の繰り返し

 

1.for の概要

今回と次回は for による処理の繰り返しを説明します。今回はグラフィックを題材に楽しく for に慣れることを目指します。

for  (読み方:for=フォー)を使うと指定した回数処理を繰り返して実行することができます。

たとえば、次の例ではメッセージの出力をを5回実行します。

for(int i = 0; i <= 4; i++)
{
    System.Diagnostics.Debug.WriteLine($"{i}回目です。");
}

Debug.WriteLineで出力される場所

この例を実行すると「0回目です。」、「1回目です。」、「2回目です。」、「3回目です。」、「4回目です。」とメッセージが順番に合計5回表示されます。

for に続く { } の中には何行でもプログラムを記述することができ、その全部が繰り返して実行されます。

 

先に書いてしまうと for はちょっと小難しくてわかりにくいです。for には複雑な機能や使い方があるのですが、2022年現在ではかなりシンプルな使い方しかされていないように私は感じます。今回は for をテーマに説明し、ちょっと細かい機能も扱いますが、あまり細部にこだわらないで基本的な使い方だけできるようになれば十分です。

別途説明する予定の foreach でも同じような処理ができ、foreach の方がわかりやすいので好まれているように感じます。たとえば、Pythonというプログラミング言語では C# の for に相当する構文はありません。foreach に相当する構文はあります。

 

2.for による繰り返しの実践

2-1.1個の円

for ははじめて見る人には複雑に見えると思うので、構文を説明する前にサンプルプログラムを作って少し肩慣らししましょう。

実際に一緒に試してみることをお勧めします。

やってみる方は次の設定で新しいプロジェクトを作成してください。

プロジェクトテンプレート Windows フォーム アプリ
プロジェクト名 ForSample
ソリューション名 ForSample
フレームワーク .NET 6.0

 

簡単なコンピューターグラフィックス(CG)を書いてみます。

 

まずは、for を使わないで赤い円を1個書いてみます。

フォームの Paint イベントハンドラーを生成して次の通りプログラムしてください。(→ 参考:イベントハンドラーの作成方法)

たとえば、次のように記述すると赤い円が表示されます。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    //全体を黒で塗りつぶします。
    e.Graphics.Clear(Color.Black);

    //赤い円を描画します。
    e.Graphics.DrawEllipse(Pens.Red, 160, 60, 450, 320);
}

少しつぶれた円にしてみました。

1つの円を描画するので、DrawEllipse メソッドを1回使用しています。

 

2-2.5個の円

それでは、座標をちょっとずつ変えて、5個の円を書いて見ましょう。今度はまん丸にしてみます。

5つの円を描画するので、DrawEllipseメソッドを5個記述してみます。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    //全体を黒で塗りつぶします。
    e.Graphics.Clear(Color.Black);

    //5つの赤い円を描画します。
    e.Graphics.DrawEllipse(Pens.Red, 20, 20, 380, 380);
    e.Graphics.DrawEllipse(Pens.Red, 30, 30, 380, 380);
    e.Graphics.DrawEllipse(Pens.Red, 40, 40, 380, 380);
    e.Graphics.DrawEllipse(Pens.Red, 50, 50, 380, 380);
    e.Graphics.DrawEllipse(Pens.Red, 60, 60, 380, 380);
}

次のようになります。

20, 30, 40, 50, 60, という座標はちょうどいいように私が調整した座標です。お好みの座標に変えてもよいのですが、この後の説明で使いますのでとりあえずこの値を使っておいてください。

 

 

2-3. for で書き換える

DrawEllipseを5回記述している部分を、for を使った繰り返し処理に書き換えてみます。

次のように書くと DrawEllipseは5回実行されますが、5回とも同じ座標・同じサイズを指定しているので、結果として1つの円しか表示されません。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    //全体を黒で塗りつぶします。
    e.Graphics.Clear(Color.Black);

    //5回DrawEllipseを実行しますが、同じ座標なので結果は1つの円に見えます。
    for (int i = 0; i <= 4; i++)
    {
        e.Graphics.DrawEllipse(Pens.Red, 20, 20, 380, 380);
    }
}

for の機能をうまく使うことでちょっとずつ座標を変えて円を描画することができます。次から for の構文を交えて実現してみましょう。

 

3.繰り返しごとに変化させる

3-1.セクション

ここからが今回のテーマである for の 説明です。

for は直後に続く { } 内のコードを記述した条件の範囲内で繰り返し実行します。1行だけしかない場合は { } はなくてもOKですが、統一感のため { } を付けるようにするとわかりやすくなってお勧めです。

 

for の ( ) の中は ; で区切られた3つのセクション(部分)から成り立っています。

各セクションには図で書いたような特別な役割があります。

初期化子セクション 最初の1回だけ実行される処理を記述します。通常は変数を初期化します。
ループにより何度繰り返し実行が発生しても初期化子セクションが再実行されることはありません。
int i = 0, j = 0 のように複数の変数の初期化を実行することもできます。
条件セクション ループが実行される条件を記述します。if文の条件式と同様に bool型 で評価できる式を書きます。
false と評価された場合直ちに for ループは終了します。
最初の時点で false の場合、処理は1回も実行されません。
反復子セクション { } 内の処理が終了した直後に実行されます。
つまり、1回の処理は「条件セクション → 処理本体 → 反復子セクション」という順で実行されます。
カンマで区切って複数の処理を記述することもできます。

この3つのセクションそれぞれに小さなプログラムを書けます。こんなところに ; で区切ってプログラムを書くとは奇妙に見えますが、これは(C#の前身であるC言語も含めて)何十年も前からある for 専用の特別ルールです。

この3つのセクションを使ってトリッキーなプログラムを書くこともできますが好まれません。多くの人はここに決まりきったことを書きますので、ここではその使い方を説明します。トリッキーな書き方をすると人はまずいないので、妙なプログラムが記述されていると他のプログラマーが混乱します。先輩にも怒られることになります。

まず、初期化セクションで初期値が0の数値型の変数を宣言します。この変数は慣例として i という名前を付けられることがほとんどです。この変数は for の処理本体 { } 内の部分で使用できます。この変数を「カウンター変数」や「ループ変数」と呼ぶ場合もあります。

次に、条件セクションでは変数 i を使ってループを継続する条件を書きます。この例では i <= 4 となっているので、 i が 4 以下であればループを継続して、もう1回処理を実行します。

最後に、反復子セクションでは i の値を更新します。反復子セクションはループで繰り返しされるたびに実行されます。この例では  i++ と書いてあるので、ループが繰り返されるたびに i の値が 1 ずつ増えていくということです。

全体としてこの for 文は 「変数 i は 0 からスタートして、ループするたびに +1 され、4より大きくなったらループを終わる」という意味になります。 つまり、i  の 値が最初は0、次は1、その次は2, その次は3, その次は4 、ここで条件セクションの値が false になるので for の処理は終了。全部で5回実行されるというわけです。

繰り返すたびに i の値が変化するので、これを使って繰り返し処理を少しずつ変えることができるのポイントです。

 

3-2.小技

Visual Studio のコードエディターでは キーボードから  f o r に続けて TAB を2回押すと自動的によくある for の形が入力されます。

慣れないうちは for の入力は苦痛だと思いますので、うまく活用してください。

 

C# では基本的は区切り位置で自由に改行できるので、お好みであれば セクションごとに改行することもできます。

たとえば、次のような感じです。

for (int i = 0; 
     i <= 4; 
     i++)
{
    //処理本体
}

各セクションに記述する内容が多い場合は、改行した方が見やすくなります。

 

3-3.繰り返すたびに座標を変化させる

今回は変数 i を使って 20 +  ( i × 10 ) の値を座標にすればDrawEllipseメソッドを5回使った例の場合と同じ座標で実行できるようになります。

繰り返し回数 変数 i の値 20 + i * 10 の値
1回目 0 20
2回目 1 30
3回目 2 40
4回目 3 50
5回目 4 60

 

 

これをプログラムで書くと次のようになります。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    //全体を黒で塗りつぶします。
    e.Graphics.Clear(Color.Black);

    //5つの赤い円を描画します。
    for (int i = 0; i <= 4; i++)
    {
        e.Graphics.DrawEllipse(Pens.Red, 20 + i * 10, 20 + i * 10, 380, 380);
    }
}

これでちょっとずつ座標が違う5つの赤い円が描画されることになります。

 

3-4. 反復子セクションで10ずつプラスする

変数 i の値が 1 ずつ増える理由は、反復しセクションで i++ と記述しているからです。これを i += 10 と記述すれば10ずつ増えることになるので、座標の計算で ×10 する必要がなくなり、少しプログラム量が減ります。やってみましょう。

10プラスすると1回で4を超えてしまうため、条件セクションもこれに合わせて i <= 4 から i <= 40 に変えておきます。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    //全体を黒で塗りつぶします。
    e.Graphics.Clear(Color.Black);

    //5つの赤い円を描画します。
    for (int i = 0; i <= 40; i+=10)
    {
        e.Graphics.DrawEllipse(Pens.Red, 20 + i, 20 + i, 380, 380);
    }
}

これでちょっとだけ少ないプログラム量で同じ結果になりますが、反復子セクションの記述は i++ と書かれることが多いので、多少プログラムが増えてもこの慣例に従ってプログラムの式の方で吸収してしまう人も多いようです。

 

3-5. 初期化子セクションで20で初期化する

i の初期値を20から開始するともっとプログラム量を少なくできますね。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    //全体を黒で塗りつぶします。
    e.Graphics.Clear(Color.Black);

    //5つの赤い円を描画します。
    for (int i = 20; i <= 60; i+=10)
    {
        e.Graphics.DrawEllipse(Pens.Red, i, i, 380, 380);
    }
}

プログラムの記述量が少ないと、プログラムが見やすくなる傾向があるので、一般的にはプログラムがはかどり、バグの発見の能率もあがります。しかし、プログラムの記述量を少なくすることで返ってわかりにくくなってしまうこともあるので、プログラムの記述量が少ないことが常に良いわけではありません。今回、同じように5つ円を描く4つのプログラムを紹介しましたが、どれがわかりやすかったでしょうか?

 

 

4.200個の円を描く

4-1.5個の同心円

せっかくなのもう少し遊びながら for の理解を深めましょう。

円を5個描くくらいの繰り返しならコピー&貼り付けで書くことも大して苦ではないのですが、for を使うと100回でも1000回でも簡単に繰り返せます。その威力がわかるプログラムを作ってみます。

5回くらいの繰り返しでも、後で機能追加や修正が必要になったときのことを考えると、for ~ Nextなどの繰り返し処理にしておいたほうが断然楽です。

 

今度は中心が同じで大きさが違う円を書いてみます。

一番外側の円はフォームの描画領域にぴったりのサイズにしてみます。 フォームの描画領域のサイズはClientSizeプロパティ(読み方:ClientSize=クライアントサイズ)で取得できます。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    //全体を黒で塗りつぶします。
    e.Graphics.Clear(Color.Black);

    //描画領域の幅と高さを取得
    int yoko = this.ClientSize.Width - 1;
    int tate = this.ClientSize.Height - 1;

    //5つの緑の同心円を描画します。
    using Pen pen = new Pen(Color.Green, 4); //太さ4の緑のペン
    e.Graphics.DrawEllipse(pen, 0, 0, yoko, tate);
    e.Graphics.DrawEllipse(pen, 10, 10, yoko - 20, tate - 20);
    e.Graphics.DrawEllipse(pen, 20, 20, yoko - 40, tate - 40);
    e.Graphics.DrawEllipse(pen, 30, 30, yoko - 60, tate - 60);
    e.Graphics.DrawEllipse(pen, 40, 40, yoko - 80, tate - 80);
}

 

4-2.using

Penの定義の前に付いている using (読み方:using=ユージング)はこのペンが不要になった時(つまり、スコープ外になった時 = Form1_Paintの実行が終了した時)にこのペンが使っているリソースを開放することをC#に指示するものです。紛らわしいのですが、よくプログラムの先頭に using System.Collections ような記述がありますが、ここで使われる using は変数宣言時に使われるusingとはまったく別の機能です。(プログラム冒頭付近の using の方は大雑把に言うと記述を省略できる名前空間を指定する機能です。)

 

プログラムを実行する際は内部でいろいろな仕組みを利用されることがあり、たかがペン1つでもメモリーやWindowsのハンドルという仕組みを利用しています。これらのリソースは有限なので派手に使うと枯渇します。枯渇しなくても見えないところで使われている仕組みのせいで何かのロックなど思わぬ影響がでることがあります。

usingを使っても使わなくてもリソースは解放されますが、usingを使った場合、不要になったタイミングですぐに解放されるので、使える場合は使うのが定石です。このプログラム程度であれば、using を省いても実害はありません。

using は開放すべきリソースがあるクラスに対してのみ使用できます。String など開放すべきリソースがないものに対して使用するとエラーで赤い波線が表示されるのですぐにわかります。開放すべきリソースがあるクラスは必ず Dispose という名前のメソッドを持っていることになっているのでこのメソッドの有無でも区別できます。

 

 

4-3.リサイズ対応

さらに、マウスなどで フォームのサイズを変えると、円のサイズも変わるようにしましょう。フォームのサイズが変わるとResizeイベント(読み方:Resize=リサイズ)が発生するので、Resizeイベントハンドラーを生成して次のように記述してください。(→ 参考:イベントハンドラーの作成方法)

private void Form1_Resize(object sender, EventArgs e)
{
    this.Invalidate();
}

 

 

それから、プロパティウィンドウを使ってFormのDoubleBufferedプロパティ(読み方:DoubleBuffered=ダブルバッファード)を True にしておいてください。このプロパティがTrueだと、内部処理でダブルバッファリングという手法を使って再描画が実行されます。この方式はグラフィックスのちらつきを抑えてきれいに再描画を実行してくれるので、変化するグラフィックスを扱うプログラムでは必須です。

前置きが長くなりましが、実行すると緑色の円が5個描画され、マウスでフォームのサイズを変更すると円のサイズも変わることが確認できると思います。

 

 

4-4. for で書き換える

では、for を使ったプログラムに書き換えてみましょう。

技術的には既に説明した内容なので、腕試しで少し自分で書き換えを試してみるのも良いと思います。

 

ということで、少し、下にスクロールしないとプログラムが見えないようにしておきます。

 

 

 

 

↓ もっと下です。

 

 

 

 

↓ まだ下です。

 

 

 

 

↓ もうちょっとです。

 

 

 

 

このような感じになります。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    //全体を黒で塗りつぶします。
    e.Graphics.Clear(Color.Black);

    //描画領域の幅と高さを取得
    int yoko = this.ClientSize.Width - 1;
    int tate = this.ClientSize.Height - 1;

    //5つの緑の同心円を描画します。
    using Pen pen = new Pen(Color.Green, 4); //太さ4の緑のペン
    
    for (int i = 0; i <= 4; i++)
    {
        int position = i * 10;
        int deltaSize = i * 20;

        e.Graphics.DrawEllipse(pen, position, position, yoko - deltaSize, tate - deltaSize);
    }
}

 

 

 

4-5.50個の同心円

ここまでできれば、いくつでも同心円を描けます。

たとえば、条件セクションで i <= 4 としている場所を 4から50に変更するだけです。やってみてフォームサイズをいろいろ変えると次のような模様が現れます。

このプログラムでは外接四角形の大きさをひき算で計算しているところがあり、変数 i の値が大きくなると値がマイナスになってしまいます。その場合エラーになるのではなく、通常と反対の方向に大きさをもった外接四角形という意味になるのでこのような模様になります。

 

4-6.200個の色彩豊かな同心円

最後にカウンター変数を使って色も変化させてみます。きれいに見えるようにいくるか数値も調整しています。

private void Form1_Paint(object sender, PaintEventArgs e)
{
    //全体を黒で塗りつぶします。
    e.Graphics.Clear(Color.Black);

    //描画領域の幅と高さを取得
    int yoko = this.ClientSize.Width - 1;
    int tate = this.ClientSize.Height - 1;

    //200個の同心円を描画します。    
    for (int i = 0; i <= 999; i+=5)
    {
        int position = i * 2;
        int deltaSize = i * 4;

        //変数 i の値から色を合成してペンを作る
        Color color = Color.FromArgb(i % 200 + 55, i % 160 + 95, i % 123 + 122);
        using Pen pen = new Pen(color,2);

        e.Graphics.DrawEllipse(pen, position, position, yoko - deltaSize, tate - deltaSize);
    }

}

これで 大分カラフルになります。マウスでフォームの大きさを変更すると単純な円が複雑に絡み合って意外な模様を見せてくれます。綺麗なのでぜひ実際にやってみてもらいたいです。

フォームの大きさを変えたときに円がちらつくようであればフォームのDoubleBufferedプロパティをtrueにするのを忘れているかもしれません。確認してみてください。

Windowsフォームのグラフィックスでは色をColor構造体で表現します。具体的な色をどのように定義するかはいくつか考え方があります。Color構造体のFromARGBメソッド(読み方:FromARGB=フロムエーアールビージー)は、赤・緑・青を合成して色を定義することができます。赤・緑・青は引数で指定し、それぞれ0~255の範囲です。全部 0 にすると黒になります。全部255にすると白です。FromARGB(255, 0, 0) は赤だけで最大なので赤です。FromARGB(200, 0, 150) は赤と青の合成ですが、赤の方が少し多いので、赤っぽい紫になります。こんな具合で数値から色を生成できるので、変数 i をうまく利用して少しずつ違う色を作成できるというわけです。255を超える値を指定するとエラーになるので、うまい具合に255いかに収まるように値を変化させていく必要があります。今回は % 演算子を使いました。この演算子はわり算のあまりを取得できます。だから i % 255 は i ÷ 255 のあまりという意味になるので、絶対に255を超えません。こういうとき便利に使えます。赤・青・緑が全部同じ値になるとグレーになってしまうので、割る数を変えてグレーが生成されないようにしています。また、値が小さいと黒っぽい目立たない色になってしまうので固定でいくつか値をたし算して明るさを底上げしています。

 

この例では変数 i は 0 から始まり、 i+=5により5ずつ増加し、999以下であればループが継続するので全部で200個の円が描画されることになります。for を使うことで簡単に処理を繰り返せること、それに、まったく同じ処理を繰り返すわけではなく、カウンター変数を使って処理に変化がつけられることを理解していただけたと思います。

 

5.練習問題

問1.何回 Hello! が出力されますか?
for (int i = 0; i < 3; i++)
{
    System.Diagnostics.Debug.WriteLine("Hello!");
}
2
3
4

 

問2.何回 Hello! が出力されますか?
for (int i = 10; i < 10; i++)
{
    System.Diagnostics.Debug.WriteLine("Hello!");
}
0
1
無限

 

問3.一番最後の行には何が出力されますか?
for (int i = 5; i < 30; i+=10)
{
    System.Diagnostics.Debug.WriteLine($"i={i}");
}
i=25
i=30
i=35

 

次の回でも、引き続き for を扱います。break や continue、いろいろな実例を紹介します。

C#初級講座講座 次の回へ