はじめに
この記事は、初心者が躓きやすいfor文について、徹底解説を行っている記事です。
「for文を書くと何が起こるのかがわからない」や「int i = 0って?」といった疑問がこの記事で解消されれば嬉しいです。
for文の基礎的な書き方については以下の記事で解説しているので、「そもそもfor文とは?」という方はこちらをご覧ください。
for文の中身
for文の中身は、大きく4つのパーツに分けることができます。
for(【初期化処理】;【ループ終了判定】;【ループ毎の処理】)
{
【実行処理】
}
例えば、3回「Hello!」と表示する場合はこのように書きます。
for(int i = 0; i < 3; ++i)
{
std::cout << "Hello!" << std::endl;
}
処理の流れを図にすると、このようになります。
flowchart TD 1["変数 i を 0 で初期化 (int i = 0)"] 2[" i が 3 未満か判定 (i < 3)"] 3[" i に 1 を足す (++i)"] 4["Helloと表示する"] 1 --> 2 2 -->|iが3未満|4 2 -->|iが3以上|ループ終了 4 --> 3 3 --> 2
それでは、それぞれについて詳しく説明していきます。
初期化処理
初期化処理とは、for文の開始時に一度だけ実行される処理のことです。
一番よく使われるのは、int i = 0 です。
この場合は、「int型の、iという名前の変数を、0で初期化する」ということになります。
なぜ int i = 0 なのか
なぜ、int i = 0 なのでしょうか?その理由を解説していきます。
なぜ int が付いているのか
C++の前身であるC言語では、for文はこのように書いていました。1
int i;
for(i = 0; i < 3; ++i)
{
// 何かしらの処理
}
for文の外に i という変数が作られています。
そして、for文の初期化処理で i に 0 が代入されています。
C++でもこのような書き方をすることができますが、この書き方では「 i が for文の外でも使えてしまう」という問題が発生します。
int i;
for(i = 0; i < 3; ++i)
{
// 何かしらの処理
}
std::cout << i << std::endl; // 2と表示される
プログラミングでは、「変数を使える範囲は小さいほうが良い」という考えがあるため、for文の外で i が使えると、あまりよくない訳です。
そこで、C++では初期化処理の中で変数を作れるように改良されました。
for(int i = 0; i < 3; ++i)
{
// 何かしらの処理
}
std::cout << i << std::endl; // コンパイルエラー! iはここでは無効
これにより、間違ってfor文の外で i の変数を使ってしまうのを防ぐことができます。
また、i がfor文の中でしか使われていないことが確定するので、コードが読みやすくなります。
なぜ i なのか
for文の中で使う変数は、慣習として i という名前を使います。これは一体なぜでしょうか?
大前提ですが、絶対に i じゃないといけないという訳ではありません。
// 変数名が hoge でもOK
for(int hoge = 0; hoge < 3; ++hoge)
{
// 何かしらの処理
}
また、for文の中でfor文を使う「2重for文」においては、j がよく使われます。
3重forだと k がよく使われます。
for (int i = 0; i < 3; ++i)
{
// i を使うと変数名が被るので、jが使われる
for (int j = 0; j < 3; ++j)
{
// 3重forは k が使われる
for (int k = 0; k < 3; ++k)
{
// 何かしらの処理
}
}
}
i を使う理由は諸説ありますが
- FORTRANというC言語よりも古い言語で、変数名の先頭が i~n は整数として扱われたから
- index, iterator, integerなど、ループと相性のよい単語が i から始まるから
- 数学の慣習で、添え字が i であることが多かったから
などがあります。
i のあと、j, k, … と続いていくのは、単純にアルファベットが i → j → k → … の順番だからです。
とりあえず変数名に困った場合は、i を使っておけば問題ないです。
なぜ i を 0 で初期化する必要があるのか
- i に値を入れる必要性
-
まず初めに、 i には 0 に限らず何かの値を入れる必要があります。それは何故でしょうか?
その理由は、変数は作られた時点では中に何が入っているかは不定となるからです。
不定というのは、「何が入っているのか分からない」ということです。もしかしたら 0 が入っているかもしれないし、4328909 のように意味の分からない数値が入っているかもしれません。
// ※このコードは、比較的新しいコンパイラだとエラーになります // ここで i の初期値は何になるか分からない for(int i; i < 3; ++i) { // 何かしらの処理 }
上記のコードで、もし i の初期値が 100 であれば、初めから 3 以上なのでfor文の中身の処理がされません。
また、-100 が初期値になった場合は 103 回もfor文の中の処理をすることになります。しかも厄介なのが、プログラムを実行するたびに初期値が変わるということで、思わぬ不具合が起きてしまいます。
なので、少なくとも i の初期値には何かを入れないといけません。
変数の初期値が不定というのは、for文の中の i に限った話ではありません。
変数を作った後は、何らかの値を入れる癖をつけておきましょう。 - なぜ初期値が 0 なのか
-
なぜ i を 0 で初期化するのかを説明します。
大前提として、絶対に 0 で初期化しないといけない訳ではありません。
// 123で初期化をして、3回処理させる。 for(int i = 123; i < 126; ++i) { // 何かしらの処理 }
しかしこれだと、何回処理が実行されるのかをパッと見ただけでは分かりにくいです。
0で初期化することによって、何回処理が実行されるのかが視覚的に分かりやすくなります。
// i < 3 の部分を見ると、3回処理が実行されるのがわかる for(int i = 0; i < 3; ++i) { // 何かしらの処理 }
「じゃあ、”int i = 1; i <= 3″でもいいのでは?」と思いますよね。
// これでも3回処理することがパッと見て分かる for(int i = 1; i <= 3; ++i) { // 何かしらの処理 }
しかし、配列を扱う場合は配列の番号が0から始まるため、0で初期化したほうが都合がいいです。
// 配列の中身を0で埋めるコード int ary[3]; for (int i = 1; i <= 3; ++i) { // i を 1 で初期化すると、i - 1 をしないといけない ary[i - 1] = 0; // 0 で初期化していたら、↓のように書けるのですっきり // ary[i] = 0; }
このような理由から、i は基本的に 0 で初期化することが望ましいです。
もちろん、意図的に 1 で初期化したり、それ以外で初期化することもあります。
しかし、単純に「〇〇回処理したい」という場合は 0 で初期化するのが良いです。
ループ終了判定
ループ終了判定とは、for文を終了するかどうかの判定に使用する条件式のことです。
この条件式を満たし続けている間は、ループし続けます。
例に挙げているように、i < 3 と書くと i が 3 未満の間はループし続けるということになります。
もしここに 0 < 1 のように、常に満たし続ける条件を書くと、for文のループを抜けることができなくなります。
for(int i = 0; 0 < 1; ++i)
{
// 永遠にここの処理が実行される
}
逆に、1 >0 のように絶対に満たさない条件を書くと、for文の中身が実行されずに即座にループを抜けます。
for(int i = 0; 1 < 0; ++i)
{
// 絶対にここに書いた処理が実行されない
}
この終了判定には、特別な意図がない限りは絶対にいつか終了する判定を入れておくのが良いでしょう。
終了しない判定式を入れてしまうと、無限ループと呼ばれる不具合のもとになります。
ループ毎の処理
ループ毎の処理とは、for文の中に書いた処理が終了した後に実行される処理です。
以下の例では、for文の中に書いた処理が終了すると、++i という処理が呼ばれます。
for(int i = 0; i < 3; ++i)
{
// 何らかの処理
}
++i は 「変数 i に 1 を足す」という処理なので、ループ毎に i が 1 増えていくことになります。
これによって、ループ終了判定である i < 3 を満たさなくなり、for文を抜けるようになります。
人によっては、++i ではなく i++ と書く場合もあります。
for文の動きは全く同じになるので、好みで大丈夫です。
また、++i を「i += 2」という処理に変更すると、「変数 i に 2 を足す」という処理になるので、2回しかfor文の中身が実行されないです。
// 一周目 i が 0
// 二週目 i が 2
// 三週目 i が 4 で 3以上になってループを抜ける
for(int i = 0; i < 3; i += 2)
{
// 何らかの処理
}
また、i に関係ない処理も書くことができます。
試しに以下のように書いてみると、for文の中に処理を書かなくても「Hello!」と表示されます。
// 無限ループになるので、終了する際は実行ウィンドウを閉じてください
for(int i = 0; i < 3; std::cout << "Hello" << std::endl)
{
}
しかし、絶望的に読みにくいので極力使わないようにしましょう。
ループ毎の処理には、よほどの理由がない限りは i を増減させるような処理を書きましょう。
おわりに
これで、基本的なfor文の動きを理解できたでしょうか?
改めて、簡単なfor文のコードを眺めてみましょう。
for(int i = 0; i < 3; ++i)
{
// 何らかの処理
}
どうでしょうか?この記事を読み始めたときよりも処理の内容が理解できるようになったと思います。
for文を理解すれば、あらゆることができるようになります。
例えば、「数あてゲーム」程度であれば、ほとんどの処理をfor文で作ることができます。
このコードについては理解できなくても大丈夫です。
「for文をマスターすれば、これくらいできるんだなぁ」という気持ちで流し見してください。
#include<iostream>
int main()
{
// answerをランダムにするとより面白いゲームになる
int answer = 10;
int input = 0;
for (std::cout << "数あてゲーム開始" << std::endl; input != answer; std::cout << (input == answer ? "正解です" : "違います") << std::endl)
{
std::cin >> input;
}
return 0;
}
皆さんの理解の手助けができたなら幸いです。
ここまで読んでいただき、ありがとうございました。
- 昔のC言語では、変数の宣言は関数の先頭で行わなければならないという制約があったため ↩︎
コメント