このページでは、for文を説明します。for文を理解すると、同じコードを何度も何度も繰り返し実行させられるようになります。これまでのページで少しずつ改良を続けてきた電卓のプログラムをさらに改造して、計算式を何度も入力・実行できるようにしてみましょう。
このページの解説は C++14 をベースとしています。
以下は目次です。要点だけをさっと確認したい方は、「まとめ」をご覧ください。
「switch文」のページでは、switch文を使って、実行する処理を切り替えられることを説明しました。電卓プログラムは、入力された演算子の種類に応じて計算する式を切り替えられるようになり、少し電卓らしくなりました。
しかし、今の電卓プログラムには、実行するたびに1つの入力と計算しかできないという不満が残っています。色々な式の入力を繰り返したいと思ったら、プログラムを再度実行しなおす必要があります。このページではこの問題の解決に取り組むことにします。
必要なことは、さきほど文章の中にちらっと登場した「繰り返し」です。プログラミングではよく、ループ (loop) という言葉で表現しますが、同じコードを何度も繰り返し実行する構造のことをいいます。
やりたいことを書き表すとこうなります。
#include <iostream>
#include <string>
int main()
{
constexpr auto addition = '+';
constexpr auto subtraction = '-';
constexpr auto multiplication = '*';
constexpr auto division = '/';
// ここから ------>
std::cout << "Please enter the formula.\n";
int value1 {};
std::string op {};
int value2 {};
std::cin >> value1 >> op >> value2;
switch (op[0]) {
case addition:
std::cout << value1 + value2 << "\n";
break;
case subtraction:
std::cout << value1 - value2 << "\n";
break;
case multiplication:
std::cout << value1 * value2 << "\n";
break;
case division:
std::cout << value1 / value2 << "\n";
break;
default:
std::cout << "The formula is incorrect.\n";
break;
}
// <----- ここまでのコードを、気が済むまで繰り返したい
}
コメントで書いたとおりですが、電卓プログラムの中核となっている部分、つまり「入力を受け取って、計算して、結果を出力する部分」を何度も繰り返したいということです。
ループさせるものは「まったく同じ内容の処理」ではなく「同じコード」であることに注意してください。変数を使っているコードをループさせるのなら、その変数に入っている値は毎回異なるかもしれませんし、入力を受け取るコードをループさせるのなら、実際に入力されてくるデータは毎回異なるでしょう。
繰り返す回数が固定的であれば、コードを必要な回数分だけコピーアンドペーストすることで、無理やり実現させることも不可能ではありませんが、プログラムが肥大化して管理しづらくなりますし、プログラマーの労力も大きいです(1万回繰り返したいといわれたらどうしますか?)。また、今回の電卓プログラムのように、繰り返す回数が固定的でなく、“実行した人がやめたくなるまで” のようなケースでは、どれだけコピーアンドペーストしていいのか分かりませんから、この方法では実現できません。
C++ でループを実現する方法はいくつかあります。このページではそのうちの1つである for文 (for statement) を取り上げます。
for文の文法は次のようになっています。
for (初期化文 条件式; 式)
繰り返したい文
これまでのページでも何度か説明していますが、「式」と「文」はきちんと区別しましょう。
式は、評価されて(計算などをして)値になるものです。たとえば、n + 5
は式であり、評価(変数 n の値に 5
を加算する)して、値(計算結果)になります。値になるので、それをさらに代入することが可能です(たとえば、answer = n + 5
)。代入も式なので、さらに連結して、value = answer = n + 5
のようにつないでいけます。
文は、いくつかの式を含んだものですが、最終的な値がありません。一連の式の処理がすべて落ち着くまでが1つの文といえます。たとえば、さきほどの最後の式の末尾に
;
を付けて、value = answer = n + 5;
とすると、これ全体で、一連の式が完結した1つの文になります。
n + 5
は式、n + 5;
は文ですから、構文を書き表すときには、「文;」という表現は正しくなくて、「式;」か「文」とすべきです。さきほどの
for文の文法に戻ると、「初期化文」「繰り返したい文」には ;
を付けていません。「条件式」には ;
を付けています。「式」には ;
がありませんが、これは
)
で終わりが分かるからです。
本当は最後の「式」にも ;
が付いたほうが一貫性があるという考え方もありますが、少なくとも C++
はそうなっていません。;
を入れるとコンパイルエラーになってしまいます。
具体的なコードで確認します。
for文を使う目的として多いのは、一定の回数だけループしたいというものです。たとえば、3回だけループしたいときには、次のように書きます。
for (int i = 0; i < 3; i = i + 1)
std::cout << "Hello\n";
まず、「初期化文」のところに int i = 0;
と書きました。プログラムの実行中、for文のところにやってくると、まずここが実行されます。「初期化文」とありますが、必ずしも変数を宣言して初期化するという用途で使わなければならないわけではなく、ほかの目的の文でも書けます。
なお、ここでは int i = 0;
という従来的な初期化スタイルになっていますが、int i {0};
のように書いても構いません。
変数名の i は、for文でよく使われる伝統的な名前です。「変数名は意味が分かりやすいようにせよ」と何度か書きましたが、i という名前は for文で使う場合にかぎっては、十分に分かりやすい名前です。
「初期化文」の実行を終えた直後、まず「条件式」がチェックされます。「条件式」のところには
i < 3
と書いています。これは、コードの繰り返し実行を続ける条件を表していて、i < 3
は「変数 i の値が 3
より小さいあいだ」を意味しています。条件式の記述のしかたについては、きちんと取り上げると長くなるので、このページでは深入りしませんが、for文の典型的な使い方をしている限りは、i < 繰り返す回数
であると思って問題ありません。3回繰り返したいから
i < 3
、5回なら i < 5
ということです。
「条件式」をチェックした結果、繰り返す条件が満たされていたら、「繰り返したい文」へ進みます。繰り返す条件が満たされていなかったら、for文の実行は終わりです。
「繰り返したい文」には、std::cout << "Hello\n";
と書いてあります。したがって、「“Hello”
という文字列を出力する」というコードを繰り返したいということです。3回繰り返すのですから、
Hello
Hello
Hello
という結果を期待しているわけです。
実際の動きとしては、「繰り返したい文」が一気に3回実行されるわけではなく、まず1回だけ実行されます。1回の実行が終わったら、「式」のところに進みます。「式」には
i = i + 1
と書かれているので、変数 i の値が
1つ増えます。変数 i の初期値は 0
ですから、「繰り返したい文」を1回実行したあとに 1
になります。結局のところ変数 i
は、現在までに「繰り返したい文」が何回実行済みであるかをあらわしていることになります。
なお、「初期化文」で宣言した変数の値を、「繰り返したい文」の中で使っても構いません。ただし、その変数の値を書き換える行為は避けたほうがいいです。ループさせる回数がおかしくなってしまいます。
「式」の実行を終えたら、再び「条件式」をチェックします。繰り返す条件が満たされていたら「繰り返したい文」へ進み、満たされていなかったら、for文の実行は終わりです。あとは同じ流れで、「繰り返したい文」>「式」>「条件式」のところを、「条件式」の内容を満たさなくなるまで、ぐるぐると回り続けます。
for文で「ある回数だけ繰り返す」という使い方は、古くからよくある典型的なものです。そのため、昔からよくある書き方があって、それは次のようなものです。
for (int i = 0; i < 3; ++i) {
std::cout << "Hello\n";
}
最初に示したコードとの違いは、以下の2箇所の書き方だけです。動作に違いはありません。
i = i + 1
が ++i
になった){
と }
で囲まれているこれらの違いについては、この後で解説します。
結局、一定回数のループを必要とするときに書き換えなければならないのは、「条件式」のところと、「繰り返したい文」のところだけです。
for (int i = 0; i < /*繰り返す回数*/; ++i) {
//
// 繰り返したい処理
//
}
「式」の部分が、++i
という記述になりました。+
を2つ並べた見慣れないこの表記は、インクリメント演算子 (increment operator)
と呼ばれるもので、構文は次のようになります。
++変数名
++ 変数名
【上級】正確には、「変数名」ではなく「式」です。
インクリメント演算子の効果は、対象の変数の値を +1 するというものです。インクリメント演算子を使うかどうかにかかわらず、+1 する行為をインクリメント (increment) と呼びます。
インクリメント演算子には、変数名の手前に置く前置(前置インクリメント)(pre-increment) と、変数名のうしろに置く後置(後置インクリメント)(post-increment) という2つの使い方があります。
前置でも後置でも、対象の変数の値をインクリメントするという効果は同じですが、違いがあるケースも存在します。これは後で取り上げます。
for文の「式」のところで使う限り、以下の記述はすべて同じ意味になります。
インクリメント演算子を使うと、代入の
=
が登場しないことが少し特徴的で注意すべきところです。=
がなくても、変数の値が変更されるということです。i = ++i;
のようにはしませんし、してはいけません。
【上級】i = ++i;
のような書き方をしていけないのは、1つの式の中で、同じ変数の値を2回以上変更する行為は、未定義の動作だからです[1] 。このコードでは、変数
i
の値が代入とインクリメントで2回変更されようとしています。i = i + 1;
なら、代入のときに1回変更するだけなので問題ありません。
記述が簡潔なので、基本的にはインクリメント演算子を使えばいいです。この使い方では
++i
と i++
に違いはないのでどちらを使っても構いません。
もう1つの違いである {
と }
の存在についてです。
最初に示した for文の構文は、次のようになっていました。
for (初期化文 条件式; 式)
繰り返したい文
このように、構文ルールからいって、{
と }
はありません。しかし、世の中の C++ のコードのほとんどは、{
と }
を付けています。
「繰り返したい文」とだけ書かれていると、いくらでも文を置いていいようにみえるかもしれませんが、実際にはそうではなくて、置ける文は1つだけです。そのため、次のコードは問題ありませんが、
for (int i = 0; i < 3; ++i)
std::cout << "Hello, World.\n";
次のコードは、おそらく想定した意味のコードになっていません。
for (int i = 0; i < 3; ++i)
std::cout << "Hello, ";
std::cout << "World.\n";
これだと、std::cout << "World.\n";
は
for文の「繰り返したい文」から外れていることになります。つまり、3回
“Hello,” を出力したあと、1回だけ “World.” を出力します。
「繰り返したい文」に1文しか置けないのでは不便です。そこで、{
と }
を使います。
{
と }
は、ブロック文 (block statement)
と呼ばれる文の一種で、0個以上の文をひとまとめにして、1つの文とみなすものです。これを使えば、複数の文を書いても、まとめて1つの文ですから許されるというわけです。
よく、「{
と }
は文が1つなら省略できる」と説明されますが、これは話が逆です。構文ルール上、文は1つしか置けず、2つ以上置きたいなら
{
と }
を付けなければならないのです。
書きたい文が1つだけだとしても、ブロック文を使って構いません。 さきほどの正しくないコード例のような間違いを防ぐ意味でも、むしろブロック文にした方がいいともいえます。
ブロック文を使えば、for文の「繰り返したい文」のところに複数の文が書けるので、このページの目的であった、電卓プログラムの改良ができます。
とりあえず、5回繰り返すようにしてみます。
#include <iostream>
#include <string>
int main()
{
constexpr auto addition = '+';
constexpr auto subtraction = '-';
constexpr auto multiplication = '*';
constexpr auto division = '/';
for (int i = 0; i < 5; ++i) {
std::cout << "Please enter the formula.\n";
int value1 {};
std::string op {};
int value2 {};
std::cin >> value1 >> op >> value2;
switch (op[0]) {
case addition:
std::cout << value1 + value2 << "\n";
break;
case subtraction:
std::cout << value1 - value2 << "\n";
break;
case multiplication:
std::cout << value1 * value2 << "\n";
break;
case division:
std::cout << value1 / value2 << "\n";
break;
default:
std::cout << "The formula is incorrect.\n";
break;
}
}
}
実行結果:
Please enter the formula.
1 + 2 <-- 入力した式
3
Please enter the formula.
10 - 1 <-- 入力した式
9
Please enter the formula.
3 * 5 <-- 入力した式
15
Please enter the formula.
5 / 2 <-- 入力した式
2
Please enter the formula.
-1 + 3 <-- 入力した式
2
うまくいっているようです。
さて、こうなると新たな不満点が出てきます。なにより、なぜ5回なのかという点が気になります。普通、「電卓の利用者の用事が済むまで」繰り返すべきです。5回では足りないかもしれないし、多すぎるかもしれません。
しかし、終わりたいときに終了できるようにするには、まだ少し知識が足りません。この問題にはこの先のページで取り組んでいくことにして、ここからは for文に関するほかの話題を続けます。
さきほどの電卓プログラムでは、for文の内側に変数の宣言があります(value1、op、value2)。
for文の内側にに変数の宣言を書いた場合、処理がループして、その宣言位置にやってくるたびに、新しい変数として初期化しなおされます。前のループのときの値を引き継ぐようなことはありません。
もし、前のループで計算した値を、次のループで使いたい、といった場合には、for文の外側で宣言する必要があります。
たとえば、整数の入力を5回行わせて、最後にその合計値を出力させるプログラムを作りたいとします。この場合、これまでの入力の合計値を覚えておく変数が必要ですが、その変数は for文の外側で宣言しなければなりません。
#include <iostream>
int main()
{
int total {0};
for (int i = 0; i < 5; ++i) {
std::cout << "Please enter the integer.\n";
int value {};
std::cin >> value;
+= value;
total }
std::cout << "total = " << total << "\n";
}
実行結果:
Please enter the integer.
6 <-- 入力した整数
Please enter the integer.
3 <-- 入力した整数
Please enter the integer.
5 <-- 入力した整数
Please enter the integer.
1 <-- 入力した整数
Please enter the integer.
8 <-- 入力した整数
total = 23
for文の話から外れますが、前置インクリメントと後置インクリメントで違いがうまれる場面を確認しておきます。違いがうまれるのは、結果をほかの変数に入れる場合です。
#include <iostream>
int main()
{
int value {};
int result {};
= 10;
value = ++value;
result std::cout << value << ", " << result << "\n";
= 10;
value = value++;
result std::cout << value << ", " << result << "\n";
}
実行結果:
11, 11
11, 10
変数 value の値が 10 のときに、前置・後置それぞれのインクリメントを行い、その結果を変数 result に代入しています。
いずれの場合でも、変数 value の値は 11 になっていますが、変数 result の方は、前置インクリメントでは 11、後置インクリメントでは 10 となっています。このような違いが生まれるのは、前置のインクリメント演算子と後置のインクリメント演算子では、次のようにルールの違いがあるからです。
前置の場合、オペランドが +1 され、式の評価結果はインクリメント “後” の値になります。[2]
後置の場合、オペランドが +1 され、式の評価結果はインクリメント “前” の値になります。[3]
ここでの「式」とは ++value
や
value++
のことであり、オペランドは value
のことです。
そのため、result = ++value
では、value の値が +1
され、インクリメント演算子の式の評価結果はインクリメント後の値です。評価結果を
result に代入するので、元の value が 10 だったのなら、value は 11
ですし、result も 11 になります。
一方、result = value++
では、value の値が +1
され、インクリメント演算子の式の評価結果はインクリメント前の値です(元の値が
10 なら 10 のまま)。評価結果を result に代入するので、value は 11
ですが、result は 10 です。
【上級】インクリメントと代入をおこなう式について、次のような説明がなされることがありますが、これは間違っています。
- 前置では、先にインクリメントして、そのあと代入される
- 後置では、まず代入して、その後でインクリメントされる
しかし、演算子の優先順位からいって、インクリメントよりも代入のほうが後です。つまり、“y = ++x
は y = (x = x + 1)
と等価であり、y = x++
は
y = (t = x, x = x + 1, t)
と等価です”[4]。
インクリメントが登場したついでに、デクリメント (decrement) にも触れておきます。デクリメントは、値を -1 する操作のことです。
デクリメントを簡潔に記述するために、デクリメント演算子 (decrement operator)
があります。記号は --
です。
デクリメント演算子の文法や、挙動はインクリメント演算子とまったく同じです。+1 だったのが -1 になるだけです。
--変数名
-- 変数名
【上級】正確には、「変数名」ではなく「式」です。
実行できるプログラムを挙げておきます。
#include <iostream>
int main()
{
int value {};
int result {};
= 10;
value = --value;
result std::cout << value << ", " << result << "\n";
= 10;
value = value--;
result std::cout << value << ", " << result << "\n";
}
実行結果:
9, 9
9, 10
for (初期化文 条件式; 式) 繰り返したい文
という構文をしており、次の順番に処理される
{
と }
で囲まれた0個以上の文のことfor (int i = 0; i < n; ++i) {...}
である(…
に繰り返したいコードを記述する)++
は、変数の値を +1
する(++i
または i++
)--
は、変数の値を -1
する(--i
または i--
)a = ++b;
では b が +1 され、その結果が a
に代入されるa = b++;
では b が +1 され、a にはインクリメント前の b
の値が代入される
新C++編の【本編】の各ページには、末尾に練習問題があります。ページ内で学んだ知識を確認する簡単な問題から、これまでに学んだ知識を組み合わせなければならない問題、あるいは更なる自力での調査や模索が必要になるような高難易度な問題をいくつか掲載しています。
i = i++
のような式が未定義の動作であることについて
問題の難易度について。
★は、すべての方が取り組める入門レベルの問題です。
★★は、自力でプログラミングができるようなるために、入門者の方であっても取り組んでほしい問題です。
★★★は、本格的にプログラマーを目指す人のための問題です。
次の3つの文のうち、実行したあとの変数 x の値がほかと違うのはどれですか?
次のプログラムの実行結果はどうなりますか?
#include <iostream>
int main()
{
for (int i = 0; i < 0; ++i) {
std::cout << "Hello\n";
}
}
次のプログラムの実行結果はどうなりますか?
#include <iostream>
int main()
{
int value {0};
for (int i = 0; i < 5; ++i) {
+= i;
value }
std::cout << value << "\n";
}
for文を使って、2 から順に 2 のべき乗を出力するプログラムを作成してください(21、22、23、24・・・つまり 2、4、8、16・・・と出力します)。10個出力したところで終了してください。
整数の入力を繰り返し受け取り、そのつど、現時点までの合計と平均を出力するプログラムを作成してください。全部で5回入力されたら終了とします。
九九の表を出力するプログラムを作成してください。つまり、次のような出力結果が得られるようにしてください。
実行結果:
1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81
はてなブックマーク に保存 | Pocket に保存 | Facebook でシェア |
X で ポスト/フォロー | LINE で送る | noteで書く |
RSS | 管理者情報 | プライバシーポリシー |