回れ回れよグルグルと(ループ文)
今日は、同じことを何回も繰り返してもらいます。
でも大丈夫、皆さんがやるのは、
ほんの数行のプログラムを書くことだけです。
それだけで、何回も、何十回も、いや何万回も同じことが繰り返せます。
ループ文をマスターできれば!
- ループ文がない世界
- ループ文がある世界
- ループ文は4種類
- foreach文
- while文
- for文
- do-while文
- ループよ、さらば(break)
- 次の回にスキップ(continue)
- ネストは浅く
- 実習ループ文
- まとめ
ループ文がない世界
ループ文がない世界で、繰り返しのあるプログラムを書くにはどうすればよいでしょうか。
一言でいうと、大変、大変、大きな労苦を伴います。
試しに、ループ文のない世界で、配列の各要素をすべて表示するプログラムを書いてみましょう。
// 7匹の哺乳類がいる配列を定義 string[] mammals= {"dog", "cat", "pig", "monkey", "wolf", "deer", "sheep"}; // 0番から6番まで7匹の哺乳類をすべて画面上に表示 Console.WriteLine(mammals[0]); Console.WriteLine(mammals[1]); Console.WriteLine(mammals[2]); Console.WriteLine(mammals[3]); Console.WriteLine(mammals[4]); Console.WriteLine(mammals[5]); Console.WriteLine(mammals[6]);
大変でしたが、なんとかできましたね。
では次に、ユーザーから入力がある限り、『ユーザーから入力がありました』と表示するプログラムを書いてみましょう。
// 1回目 // Console.ReadLine()は、コンソール画面から入力された文字列 // !=は、ひとしくないという意味 // ""は文字が一つもない空っぽの文字列 // 合わせて、コンソール画面からユーザーによって入力された文字列が、空っぽではないという意味 if(Console.ReadLine() != "") { Console.WriteLine("ユーザーから入力がありました"); } // 2回目 if(Console.ReadLine() != "") { Console.WriteLine("ユーザーから入力がありました"); } // 3回目 if(Console.ReadLine() != "") { Console.WriteLine("ユーザーから入力がありました"); } // 4回目 if(Console.ReadLine() != "") { Console.WriteLine("ユーザーから入力がありました"); } // 以下エンドレス
残念ながら上のプログラムではうまくいきません。
ユーザーが何回入力するか分からないからです。
4回目までならうまくいきます。
でも、5回目以降は、入力されても何もできません。
逆に、ユーザーが4回目を入力せず辞めてしまったら、どうでしょう。
そしたら、プログラムはずっと止まったままです。
このように、『繰り返して何かを行う』ことそのものを、
何かの形でかけるようにしないと、
プログラムを書くのが大変になったり、
最悪書けなくなってしまいます。
(本日の内容一覧に戻る方はこちら)
ループ文がある世界
さあ、ここで、ループ文にご登場いただきましょう。
先ほどの二つの例を、ループ文で書き直しますね。
たった数行で同じことが書けますよ。
string[] mammals= {"dog", "cat", "pig", "monkey", "wolf", "deer", "sheep"}; foreach(var mammal in mammals) { Console.WriteLine(mammal); }
while(Console.ReadLine() != "") { Console.WriteLine("ユーザーから入力がありました。"); }
では、ループ文の中身をもう少し見てみましょう。
ループ文は4種類
ループ文は4種類あります。それぞれ想定する使用目的が違うので、下のリストで概要をつかんでくださいね。
- 1. foreach文
- コンテナと組み合わせて使う
- 2. while文
- コンソール画面からの入力など、プログラム外の要素でループするか決めるときに使う
- 3. for文
-
一定の回数、ループを繰り返すときに使う
コンテナのインデックスを取得したいときに使う - 4. do-while文
- ある操作を行った後で、プログラム外の要素次第で継続するか決めるときに使う
foreach文
foreach文は配列やリストのようなコンテナと組み合わせるのが定石です。
コンテナの各要素を表示したい時や、コンテナの要素に合わせた条件分岐をする時に使います。
string[] personNames = {"Taro", "Hanako", "Jiro"}; foreach(var personName in personNames) { // 文字列の長さとは、文字列の中にある文字の個数です // 例えば、"Hello"の中には、H, e, l, l, oの5文字があるので、長さは5です。 // もし(if)、personNameという文字列の長さ(Length)が5以上(>=)なら if(personName.Length >= 5) { Console.WriteLine("5文字以上の名前です!"); } else { Console.WriteLine("4文字以下の名前です。"); } }
while文
while文は、条件を自由に指定できるループ文です。
ユーザーからの入力次第でループするか決める例をご紹介しましたね。
自由な条件を設定できる分、扱いが難しいですが、
一つの使い道は、
ユーザーとのやり取り(外部条件)で動きを変えるプログラムを作る時です。
while文には、代表的なフォーマットが2種類あります。
// フォーマット1. ループの継続を判断する真偽条件が1つしかなく、かつ簡単な時 // 真偽条件の初期設定(なくてもよい) while(真偽条件判定 { // 操作 // 真偽条件変更(次回の判定の結果を変更) } // フォーマット2. // ループの継続を判断する真偽条件が2つ以上あるとき。 // あるいは条件は1つでも複雑で、while()の()の中に書くと読みにくい時 while(true) { // 真偽条件を判定する材料のデータ var 真偽条件判定材料データの変数 = 真偽条件判定材料のデータ; // ループ自体を終了する条件 if(ループ自体を終了する条件){break;} // その回のループをスキップして、次の回のループに行く条件(不要ならなくてもよい) if(その回のループをスキップする条件){continue;} }
breakとcontinueについては後で詳しく述べます。
ここではこれだけ覚えてください。
breakは、ループ全体を終了させる。
continueは、その回のループはそこでスキップして次の回に行く。
条件はどちらもif()で指定する。
using System; namespace WhileLoopSandBox { class MainClass { static void Main(string[] args) { Console.WriteLine("文字列の入力をして下さい"); // 10文字以下の入力を受け付けたい var maxStringLength = 10; // Console.ReadLine().Lengthは、コンソール画面(Console)が読み込んだ(ReadLine)文字列の長さ(Length) while (Console.ReadLine().Length <= maxStringLength) { Console.WriteLine($"{maxStringLength}文字以下の入力です。続けてください。"); } } } }
whileループの始まりから終わりまでをご覧いただきます。
using System; namespace WhileLoopSandBox { class MainClass { static void Main(string[] args) { Console.WriteLine("文字列をご入力ください。"); // 条件が多い時のwhileループ while (true) { // 条件の判定材料となるデータ string currentUserInput = Console.ReadLine(); // ルーピング自体の停止条件 // 入力がないとルーピング終了 if (currentUserInput == "") { break; } // 各ループターンのスキップ条件 // 11文字以上だとスキップして次のループへ if (currentUserInput.Length >= 11) { Console.WriteLine("長すぎです。やり直し。"); continue; } // ループが停止もせず、スキップもされなかった時の正常な動作 Console.WriteLine("きちんとご入力いただきありがとうございます。続けてください。"); } } } }
for文
for文の基本フォーマットは以下の通りです。
直感的に分かりづらい構文なので、コメントで説明を入れます。
// ループのカウンターが、初期値から、終了値になるまでループ // 『ループカウンター++』は、ループのカウンターをループが終わるごとに1つずつ増やしますという意味 for(var ループカウンター = 初期値; ループカウンター <= 終了値; ループカウンター++) { // ループの各回で行う処理; }
for文の代表的な使い道から、2つを紹介します。
順番に見ていきましょう。
配列やリストの番号を取得して使う
配列やリストの番号と、番号に対応するデータを一度に表示したいとき、for文が使えます。
ループカウンターは、対象の配列やリストの個別データの番号を指します。それにふさわしい名前を付けましょう。
例えば、flowersという配列にfor文を使うなら、flowerIndexと名付けましょう。
ただし、対象の配列やリストが自明なら、短くindexでも構いません。
string[] flowers = {"tulip", "lily", "rose"}; // 配列やリストの番号(インデックス)を取得したいとき // flowerIndexはflowersの個別のデータの番号 // flowerIndexは0始まりで、 flowersのデータ数( flowers.Length)より1つ少ない数で終わる // ループが一つ進むごとに、 flowerIndexは1つ増える for(var flowerIndex = 0; flowerIndex <= flowers.Length -1 ; flowerIndex++) { // 繰り返したい操作; }
using System; namespace ForStatementSandBox { class MainClass { static void Main(string[] args) { string[] fishes = {"carp", "sardine", "salmon"}; for(var fishIndex = 0; fishIndex <= fishes.Length - 1; fishIndex++) { Console.WriteLine($"{fishIndex}番目の魚 : {fishes[fishIndex]}"); } } } }
指定した回数だけ何かの操作を繰り返す
指定した回数だけ、何かを繰り返すときもfor文は使います。
例えば、100回、コマンドライン画面に文字列を表示したい時です。
このような場合、ループカウンターの名前は、loopCountか、短くcountとしましょう。
// ループ終了回数がXで、X回繰り返したいとき // ループのカウントは、1回目から、終了回数まで for(var loopCount = 1; loopCount <= ループ終了回数X; loopCount++) { // 繰り返したい操作; }
using System; namespace ForStatementSandBox { class MainClass { static void Main(string[] args) { for(var loopCount = 1; loopCount <= 100; loopCount++) { Console.WriteLine($"{loopCount}回目の : for文ループ!"); } } } }
do-while文
do-while文は、1回何かの操作を必ず実行します。
そののち、条件を満たせば、その操作を繰り返します。
条件を満たさない場合は、そのまま終了です。
上記の説明だけでは分かりにくいですね。フォーマットと実例を見ていただきましょう。
do { // 必ず1回はする操作 } while(条件判定);
using System; namespace DoWhileSandBox { class MainClass { static void Main(string[] args) { do { Console.WriteLine("10文字以下の文字列を入力しよう!"); }//, then while (Console.ReadLine().Length <= 10); // redo; } } }
条件の真偽に関係なく、1回は{}の中身が実行されています。
1枚目では、最初に10文字以上入力したので、そこで終了です。
でも2枚目では、最初に10文字より短く入力したので、ループに入りました。
do-while文は、以下の構文が省略されたものと考えられます。
//まず操作1を必ずする // 操作1 // その後、条件を満たしていれば、操作1を継続する while(条件) { // 操作1 }
ループよ、さらば(break)
while文の説明で述べた通り、breakは『一連のループ自体にサヨナラする』です。
while文以外のループでも使えます。
例えば、for文やforeach文で、特別な事情があって、
既定の回数のループをしないまま、強制的にループを終了したいときです。
using System; namespace BreakSandBox { class MainClass { static void Main(string[] args) { string[] salmonsWithOtherFish = { "salmon", "salmon", "salmon", "salmon", "carp", "salmon" }; foreach (var fish in salmonsWithOtherFish) { // ループを終了する条件 // もし一匹でもsalmonでないfishが見つかったら、そこでループは強制終了 if (fish != "salmon") { Console.WriteLine($"not salmon!{fish}!"); break; } // 何もなければこの操作まで至る Console.WriteLine("salmon!"); } } } }
次の回にスキップ(continue)
while文の説明で述べた通り、
continueは『そのループをスキップして、次のループに向かう』です。
while文以外のループでも使えます。
例えば、foreach文で、
配列やリストの中の各データの性質次第では、
処理をしないまま次のループにスキップするときに使います。
using System; namespace ContinueSandBox { class MainClass { static void Main(string[] args) { string[] fishes = { "sardine", "carp", "salmon" }; foreach (var fish in fishes) { // salmonでなければ次のループにスキップ if (fish != "salmon") { Console.WriteLine("not salmon!"); continue; } // salmonであるときのみ、スキップされずこの操作に到達する Console.WriteLine("salmon!"); } } } }
ネストは浅く
ネストとは、入れ子構造のことです。
ループのネストは、できる限り一重、最悪でも二重までが望ましいでしょう。
三重以上のループでは、ループの管理が難しくなります。
皆さん自身の頭を悩ませてしまい、ミスを誘発します。
また、二重、三重ループでは、breakで簡単にループを抜け出す事はできません。
goto文を使い、
多重ループのすぐ真下に出るのが、最も現実的なやり方です。
しかし、goto文はチーム開発では原則禁止です。
なぜなら、ラベルさえつければ、プログラムのどこにでもワープできる危険すぎる構文だからです。
goto文のせいでハチャメチャなプログラムが簡単にできてしまいます。
goto文が現実的に使えない以上、ますます多重ループは避けるべきです。
using System; using System.Linq; namespace DeeplyNestedLoopingSandBox { class MainClass { static void Main(string[] args) { // 簡単な時刻表 int[] hours = Enumerable.Range(start: 0, count: 24).ToArray(); int[] minutes = Enumerable.Range(start: 0, count: 60).ToArray(); int[] seconds = Enumerable.Range(start: 0, count: 60).ToArray(); foreach (var hour in hours) { foreach (var minute in minutes) { foreach (var second in seconds) { var currentTime = $"{hour}:{minute}:{second}"; if (currentTime == "12:0:0") { Console.WriteLine($"もう{currentTime}! ループよ、さよなら!"); // 危険: goto文を使うしかなかった // 三重ループなので仕方がない goto ENTIRE_LOOP_END; } Console.WriteLine($"今は{hour}:{minute}:{second}"); } } } // 三重ループの出口 ENTIRE_LOOP_END: } } }
実習ループ文
問題1.全要素をコマンドライン画面に表示
1から1000000(百万)までの整数から作られた、
以下の配列の全要素を、コマンドライン画面に表示してください。
// 1から1000000までの整数がありますよ int[] integers_Min_1_Max_1000000 = Enumerable.Range(start: 1, count: 1000000).ToArray();
問題2. コマンドラインからの入力によるループ
ユーザーから、“yes”と入力され続ける限り、“loop!”とコンソール画面に表示するプログラムを作ってください。
問題3. breakとcontinueのコメントによる解説(初心者向け)
以下のコードでは、数字とローマンアルファベットの境目を見やすくするために、
例外的に『_』を複合語の句切れ目にしています。
using System; namespace FizzBuzzSandBox { class MainClass { static void Main(string[] args) { while (true) { int randomInteger_Min_0_Max_100 = (new Random()).Next(start: 0, count: 101); if (randomInteger_Min_0_Max_100 < 20) { break; } if (randomInteger_Min_0_Max_100 > 90) { break; } if (randomInteger_Min_0_Max_100 % 2 == 0) { Console.WriteLine($"{randomInteger_Min_0_Max_100}は20以上90以下ですが偶数です。惜しい!"); continue; } Console.WriteLine($"乱数{randomInteger_Min_0_Max_100}は20以上90以下の奇数です。"); } } } }
問題4. FizzBuzz
有名なFizzBuzz問題です。
1から100までの数について、15で割り切れるならFizzBuzzと表示してください。
15で割り切れない場合でも、3で割り切れればFizz、5で割り切れればBuzzと表示してください。
どれでも割り切れない場合は、数字そのものを表示してください。
問題5. 最初の入力次第でループするか決まるプログラム
5文字以下の文字列を入力するプログラムを書いてください。
必ず1回は実行され、5文字以下の文字列がその時に入力されたら、
引き続きループしてください。
まとめ
皆さんお疲れ様です。
これでこの連載の前半は終了です。
次回から、C#の肝、クラスについてご説明します。
クラスが使えるようになると、ますますできることが広がりますよ。
ゲーム制作関連のオススメ連載リンク
とっても手軽なゲーム制作体験!
Unityゲーム開発基礎
実際のリリースゲームを題材にしたハンズオンゲーム制作連載
実践unityゲーム開発