while文

ループ文その2

ループ文にはfor文のほかにwhile文というものもあります。
記述方法が異なるだけで機能としてはほぼ同じものです。


static void Main(string[] args)
{
    int num = 0;

    while(num < 5)
    {
        Console.WriteLine(num++);
    }

    Console.ReadLine();
}
0
1
2
3
4

while文は条件式が真の間ループを実行します。

while文は条件式のみを使用します。
for文から初期化式と反復式を省いたものとも言えます。

サンプルコードではwhile文の外でループカウンタ用の変数を宣言しています。
for文とは違い反復式がないので、ループカウンタはループ文内で適切にインクリメントする必要があります。
これを忘れると無限ループになってしまいます。

ループカウンタが必要な場合はwhile文よりもfor文を使用したほうが良いでしょう。
それ以外の場合はwhile文が使用されることが多いです。

break文とcontinue文

while文でもbreak文continue文が使用できます。


static void Main(string[] args)
{
    int num = 0;

    while(true)
    {
        if (num < 5)
        {
            Console.WriteLine(num++);
            continue;
        }
        break;               
    }

    Console.ReadLine();
}
0
1
2
3
4

while文の条件式に「true」を指定すると、そのwhile文は無限ループになります。
そのままではプログラムがフリーズしますから、break文で適切にループを抜けます。

ブロック記号の省略

while文でループしたい処理が一行で書ける場合はブロック記号を省略できます。


int num = 0;
while (num < 5)
    Console.WriteLine(num++);

もしループしたい文が全くない場合は以下のような記述が可能です。


static int Func(int num)
{
    Console.WriteLine(num);
    return num + 1;
}

static void Main(string[] args)
{
    int num = 0;

    //ループされる処理がないwhile文
    while ((num = Func(num)) < 5) ;
    
    Console.ReadLine();
}

自作メソッドFuncは、与えられたint型引数を表示し、1を加算して返します。

条件式も式の一種なので、ここにメソッドを指定すればメソッドが実行されます。
つまりループ文で使用する条件式もループされる文の一部といえます。

条件が真の場合に実行したい文がないので、すぐ後にセミコロンを書くことでループ文を終わらせています。

ちなみに、代入式を評価すると代入された値が返ってきます。
12行目の処理は、

  1. 変数numを引数にしてFuncメソッドを実行
  2. Funcメソッドの実行結果を変数numに代入
  3. 「変数numに代入された値 < 5」の条件を判定
  4. 真ならば1から繰り返し、偽ならばループ終了

という手順になります。

do while文

while文と似た働きをするものにdo while文があります。
while文との違いは、条件式がループの最後にある点です。

まずwhile文を用いて、ユーザーからの文字列入力を受け取る処理を書いてみます。


static void Main(string[] args)
{
    string str = string.Empty;

    while(str == string.Empty)
    {
        Console.WriteLine("何か文字列を入力してください。");
        str = Console.ReadLine();
    }

    Console.WriteLine("\n入力した文字列: " + str);
    Console.WriteLine("Enterキーで終了");
    Console.ReadLine();
}

ユーザー入力などがあるプログラムの場合、実際にプログラムが実行されるまではどのような値になるかはわかりません。
そのため、入力された値のチェック処理が必要です。
今回は「何も入力せずにEnterキーを押した場合」、つまり空文字を入力エラーとし、ユーザーに再度入力を求めます。
入力エラーが解消されない限り処理を進めないためにループ文を使用しています。

さて、変数strは宣言時に空文字で初期化しています。
その直後のwhile文では条件式で変数strが空文字か否かの判定が行われています。
よく考えると一回目の条件判定の時点では変数strは空文字であることは確実なので、無駄な処理が発生しています。

これをdo while文で書き直すと以下のようになります。


static void Main(string[] args)
{
    string str;

    do
    {
        Console.WriteLine("何か文字列を入力してください。");
        str = Console.ReadLine();
    } while (str == string.Empty);

    Console.WriteLine("\n入力した文字列: " + str);
    Console.WriteLine("Enterキーで終了");
    Console.ReadLine();
}

do while文は条件式がループブロックの後にあるのでループブロックが先に実行されます。
変数strはループ内で値が代入されるため、宣言時に初期化する必要がなくなります。

do while文は条件式の内容がどのようなものであれ、最低でも一回はループ文が実行されることになります。

ループとConsole.Readメソッド

Console.Readメソッドはコンソール(標準入力)から一文字読み取るメソッドです。
プログラム実行時にユーザーが何文字の文字列を入力するかは分からないため、このメソッドは多くの場合でループを使用して文字を読み取ります。


static void Main(string[] args)
{
    Console.WriteLine("何か文字を入力してください。");

    int num = 0;
    while ((num = Console.Read()) != '\n')
    {
        Console.WriteLine(num);
    }

    Console.WriteLine("Enterキーで終了");
    Console.ReadLine();
}
何か文字を入力してください。
abc
97
98
99
13
Enterキーで終了

キーボードからの入力の場合、必ず最後に改行が行われます。
そのため、改行文字が現れるまでループを繰り返すことで、ユーザーが何文字入力しようとすべて読み取ることができます。

改行文字

上のサンプルコードは「abc」の三文字の入力にもかかわらず、結果は四文字分となっています。
「97」は「a」、「98」は「b」、「99」は「c」ですが、「13」が余計です。

この「13」はキャリッジリターンという改行文字の一部です。
Windows環境では改行は「キャリッジーリターン(\r)」と「ラインフィード(\n)」の二文字で表します。
(\r\n)
MacOSX以降やLinux環境では「ラインフィード」のみとなっています。
(MacOS9以前はキャリッジリターンのみだったらしい)

エスケープ文字は「\」とのセットで一文字とみなされることに注意してください。
例えば「\n」は二文字ではなく一文字です。
(エスケープシーケンスを参照)

キャリッジリターンは内部では「13」、ラインフィードは「10」という数値で表されます。
つまり、上記コードはWindows環境で実行すると不必要なキャリッジリターンまで読み取られてしまうということです。

上記コードの問題を修正すると以下のようになります。


static void Main(string[] args)
{
    Console.WriteLine("何か文字を入力してください。");

    int num = 0;
    while ((num = Console.Read()) != '\n')
    {
        if (num == '\r')
            continue;
        Console.WriteLine(num);
    }

    Console.WriteLine("Enterキーで終了");
    Console.ReadLine();
}
何か文字を入力してください。
abc
97
98
99
Enterキーで終了

上記コードはWindow、MacOS、Linuxの環境で正しく動くはずです。
しかしMacOS9以前では正しく動きません。
(MacOS9以前でC#を動かすことはあまりないでしょうけれど)

MacOSXでは「¥(円記号)」と「\(バックスラッシュ。本来は半角)」は別の文字として認識されるので注意してください。

あらゆる環境での動作を想定するのならばEnvironment.NewLineプロパティを使用します。


static void Main(string[] args)
{
    Console.WriteLine("何か文字を入力してください。");

    int num = 0;
    while ((num = Console.Read()) != Environment.NewLine[0])
    {
        Console.WriteLine(num);
    }
    for (int i = 1; i < Environment.NewLine.Length; i++)
        Console.Read();

    Console.WriteLine("Enterキーで終了");
    Console.ReadLine();
}
何か文字を入力してください。
abc
97
98
99
Enterキーで終了

Environment.NewLineには、プログラムの実行環境ごとの改行文字がstring型で保存されています。
Windowsならば「\r\n」が、Linuxならば「\n」が得られます。

string型の文字列は配列のように添え字を使用すればchar型として一文字取り出すことができますから、「Environment.NewLine[0]」とすることで改行文字の先頭の文字が得られます。
while文の条件判定でその文字が出現するまでループを続けます。

ループ終了後、Windows環境の場合は改行文字が二文字分ありますから、標準入力ストリーム内に二文字目が残った状態になっていますので、その文字数回だけConsole.Readメソッドを実行して標準入力ストリーム内の文字を読み捨てます。
Linux環境などではfor文の条件判定を満たさないので11行目のConsole.Readメソッドは一度も実行されません。

改行文字が三文字以上の環境は今のところ存在しないので、わざわざfor文などを使用しなくても「Environment.NewLine」の長さ(Lengthプロパティ)が「2」ならば一回だけConsole.Readメソッドを実行する、という処理でも構いません。