while文 | Programming Place Plus 新C++編

トップページ新C++編

このページの概要

このページではまず、while文という、ループ構造を作る機能を紹介します。ループ構造といえば、すでに for文を紹介しているので、while文は2つ目の方法ということになります。このページではさらに3つ目の方法として do文も紹介します。また、ループと関係が深い2つの機能、break文と continue文についても取り上げています。

以下は目次です。要点だけをさっと確認したい方は、「まとめ」をご覧ください。



while文

if文」のページでは、条件に応じて処理を分岐させる方法を学びました。今度は、条件に応じて処理を繰り返す方法を学習します。つまり、ループ構造です。

ループ構造を構築する方法として、すでに for文が登場しています(「for文」のページを参照)。実のところ、このページで取り上げる内容はすべて、for文でも実現できます。ただ、for文は少し多機能すぎる面があります。

たとえば、for文は、変数 i のような、ループの周回とともに変化する変数がセットになるのが普通です。必要がなければ省略すればいいとはいえ、それはそれで少し不自然にみえます。

// 変数 i がセット
for (int i = 0; i < 5; ++i) {}

// いらなければ書かなければいいのだが、不格好
for ( ; s == "OK"; ) {}

基本的に for文は、「ある回数だけ繰り返す」というループ構造の構築に向いています。一方で、回数が明確でなく「ある条件を満たすあいだ繰り返す」というループ構造には向いていない場合があります。

そこで、「ある条件を満たすあいだ繰り返す」ループをつくる方法として while文 (while statement) があります。while文の文法は次のようになっています。

while (条件式)
    繰り返したい文

要は、for文の「条件式」だけを残したものです。「条件式」が true である間、ループします。「繰り返したい文」に書ける文は1つだけですが、ブロック文にすれば複数の文が書ける点もこれまでどおりです。

while文が作り出すコードのかたちは if文とよく似ています。if文なら、「条件式」が true だったら文を1回だけ実行する。while文なら、「条件式」が true であるあいだ、文を繰り返し実行する。ということになります。

// 変数 s の値が "OK" のあいだ、ループする
while (s == "OK") {
    // ここを繰り返す
}

// 変数 s の値が "OK のときにだけ、文を1回だけ実行する
if (s == "OK") {
    // ここを1回実行する
}

次のプログラムは、入力された文字列をそのまま出力します。“exit” が入力されたら、プログラムを終了させます。

#include <iostream>
#include <string>

int main()
{
    std::string s {};
    while (s != "exit") {
        std::cout << "Please enter the string.\n";
        std::cin >> s;
        std::cout << s << "\n";
    }
}

実行結果:

Please enter the string.
Hello  <-- 入力した文字列
Hello
Please enter the string.
exit  <-- 入力した文字列
exit

do文

続いて、do文 (do statement) を取り上げます。

do文は、while キーワードとセットにすることで、特殊なかたちのループ構造を構築します。セットなので、よく「do-whileループ」という表現がなされます。

do文の構文は次のようになります。

do
    繰り返したい文
while (条件式);

do文によるループ構造が特殊なのは、「条件式」の判定をループの末尾で行う点です。そのため、「繰り返したい文」は必ず1回以上実行されます

いつものように、「繰り返したい文」は1文だけしか書けませんが、{} で囲ってブロック文にすれば、複数の文を書けます。

while文のときに例に挙げた “exit” が入力されるまで繰り返すプログラムを do文で書くと、次のようになります。

#include <iostream>
#include <string>

int main()
{
    std::string s {};
    do {
        std::cout << "Please enter the string.\n";
        std::cin >> s;
        std::cout << s << "\n";
    } while (s != "exit");
}

実行結果:

Please enter the string.
Hello  <-- 入力した文字列
Hello
Please enter the string.
exit  <-- 入力した文字列
exit

プログラムの実行中、do文のところに来ると、そのまま無条件で「繰り返したい文」の実行に入ります。そして、「繰り返したい文」の終わりまで実行を終えたら、「条件式」がチェックされます。条件式を評価した結果が true であれば、ループは続行となり「繰り返したい文」の先頭に戻ります。false であれば、ループは終了です。

変数 s は do文の外側で宣言しなければなりません。「繰り返したい文」の中で宣言すると、「条件式」のところで s を使えないためです。

do文はあまり使われることはありません。「必ず1回は実行される」という特徴を有効に活かせる場面がそれほどないからです。while文や for文で書けるループを、わざわざ do文で書く意味はありません。

do文が適しているように思える場面でも、while文や for文で無限ループ(後述)をつくり、break文(後述)を使ってループを終えるようにするとうまく書ける場合があります。

break文

ここまでのサンプルプログラムでは、“exit” と入力された場合、“exit” が出力されてからプログラムが終了しています。しかし、終わるときには何もせず、ただちに終わってほしいこともあります。

この要求に応えるには、「ループをただちにやめる」方法が必要です。いったんループの末尾まで実行して「条件式」がチェックされるのを待つのではなく、文字通り「ただちに、今すぐ」ループをやめたいということです。

つまり、こういうことです。

std::string s {};
while (s != "exit") {
    std::cout << "Please enter the string.\n";
    std::cin >> s;

    // 入力された文字列が "exit" だったら、
    // ただちにループをやめたい(この下の文は実行したくない)

    std::cout << s << "\n";
}

「ループをただちにやめる」ために使えるのが break文 (break statement) です。

break文は switch文のところでも登場しています。そのときは switch文を終わらせる役割を持っていました(「switch文」のページを参照)。while文の中で使うと while文を終わらせる役割になります。do文でも for文でも同様です。

if文では使えません。

break文の構文は単純です。while文、do文、for文の「繰り返したい文」のところに、以下のように書くだけです。

break;

この文が実行されると、ただちにループが終了します。そのため、普通は無条件で使うことはなく、if文と組み合わせることになります。

break文を使って、“exit” が入力されたら、ただちにループを終わらせるようにしてみます。

#include <iostream>
#include <string>

int main()
{
    std::string s {};
    while (s != "exit") {
        std::cout << "Please enter the string.\n";
        std::cin >> s;
        if (s == "exit") {  // s が "exit" だったら・・・
            break;          // while をただちに終える
        }
        std::cout << s << "\n";
    }
}

実行結果:

Please enter the string.
Hello  <-- 入力した文字列
Hello
Please enter the string.
exit  <-- 入力した文字列

うまくいきました。変数 s が “exit” だったときは break文によってループが終了するので、std::cout << s << "\n"; が実行されず、“exit” の出力がなくなります。

ループがネストしているとき

ループがネストしている場合に、break文によって終了させられるのは、break文が書かれているループだけです

次のプログラムは、1~9 の整数同士を掛け合わせた結果の表を出力しますが、答えが 50以上になるものを取り除いています。

#include <iostream>

int main()
{
    for (int i = 1; i <= 9; ++i) {
        for (int j = 1; j <= 9; ++j) {
            int ans {i * j};

            if (ans >= 50) {
                break;  // A に飛ぶ
            }
            std::cout << ans << " ";
        }
        // A

        std::cout << "\n";
    }
}

実行結果:

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
7 14 21 28 35 42 49
8 16 24 32 40 48
9 18 27 36 45

for文による二重のループが構築されており、break文は内側の for文の中にあります。この break文によって終了させられるのは、内側の for文だけです。コメントにあるとおり、「A」と書かれたところへ移動することになりますが、ここはまだ外側の for文の中です。

そのため、内側にあるループから、一気に2つ以上のループを終了したいときには、1つ1つ終了させていかなければなりません。今度は、答えが 50以上になるものを見つけた時点で、プログラムを終了させるようにしてみます。

#include <iostream>

int main()
{
    for (int i = 1; i <= 9; ++i) {
        bool is_break {false};

        for (int j = 1; j <= 9; ++j) {
            int ans {i * j};

            if (ans >= 50) {
                is_break = true;
                break;  // A に飛ぶ
            }
            std::cout << ans << " ";
        }
        // A

        std::cout << "\n";

        if (is_break) {
            break;
        }
    }
}

実行結果:

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

コメントで「A」と書いた箇所にやってきたとき、内側のループが break文によって終了してきたのか、j <= 9 という条件式が満たされなくなって終了してきたのかを判断するような機能は C++ にはありません。この区別が必要なときには、自前で bool型の変数を使うなどして、自力で区別を付ける必要があります。ここでは、変数 is_break を追加し、break文で終了する直前でだけ true が代入されるようにしています。この変数の値を調べて true であれば、もう1度 break文を実行して、外側の for文からも抜け出します。

正直なところ、このような方法は分かりづらいと言わざるを得ません。現状ではこの程度の書き方しかできませんが、もう少し C++ の機能を把握すると、ほかの書き方もできるようになります。

【上級】複数のループを一度に終了させるほかの方法として、goto文(「スコープと名前空間」のページを参照)を使う方法や、関数化しておいて return文で一気に終了させてしまう方法があります。

無限ループ

break文はうまく機能しましたが、新たな不満が生まれてしまいました。while文の「条件式」と、break文を囲む if文の「条件式」が実質的に同じことをいっています。==!= の違いはあるものの、どちらも「変数 s が “exit” であればループを終わらせる」ということですから、これは同じことです。

このように同じ意味のコードを重複させることはミスの原因になります。あとから条件を変えなければならなくなったとき、すべての箇所を間違いなく修正しなければなりません。ソースコードを読むときにも、while文そのものの条件式と、break文を実行する条件とをそれぞれ理解、把握しなければなりません。いずれにしても、余分な労力です。

今回のケースでは、入力された文字列によっては、その後の処理を実行したくないということなので、break文を囲む if文には必要性があります。一方で、while文の条件式は、初回のループではまだ文字列の入力が行われていないので、そもそも正しい判定ができません。つまり、どちらかの条件判定を無くすとすれば、while文の方を無くすことになります。

そこで、while の「条件式」の方では、ループをつねに続行する条件にしてしまいます。そのためには、いつも true になればいいのですから、while (true) と書けばいいです。

#include <iostream>
#include <string>

int main()
{
    while (true) {
        std::cout << "Please enter the string.\n";
        std::string s {};
        std::cin >> s;
        if (s == "exit") {
            break;
        }
        std::cout << s << "\n";
    }
}

実行結果:

Please enter the string.
Hello  <-- 入力した文字列
Hello
Please enter the string.
exit  <-- 入力した文字列

ループを続ける条件がいつも true であるループ構造を、無限ループ (inifinite loop) と呼びます。無限ループは、本当に無限に繰り返される場合もありますし、break文などの別の手段によって抜け出せるようになっている場合もあります。

本当に無限に繰り返される無限ループは、機器の電源を切るといった操作で実行を止められるタイプのプログラムならばありえます。

無限ループは for文で書くこともできます。その際には、for (;true;) のように書いてもいいですが、for (;;) のように「条件式」を空にしてしまうのでも構いません。

また、一見して無限ループにみえなくても、結果的に無限ループになっている、あるいは意図せずに無限ループになってしまっている(つまりバグ)ケースもあり得ます。

#include <iostream>

int main()
{
    int value {};
    std::cin >> value;

    while (value != 0) {
        std::cout << value << "\n";
        value -= 2;
    }
}

while文の条件式は value != 0 となっており、一見して無限ループではなさそうです。変数 value の値は、while文の内側で 2 ずつ減らされていき、0 になったらループを抜け出すつもりでつくられています。

しかし、変数 value の最初の値が負数だったら、2 ずつ減らしていった先に 0 はありません。あるいは、5 のような奇数だったら、2 ずつ減らしていくと 5、3、1、-1、-3・・・となってしまうので、0 になることがありません。結果的に無限ループを作ってしまっています。

continue文

よく、break文とセットで解説される continue文 (continue statement) についても取り上げておきます。

continue文はループの内側でのみ使用でき、現在の周回での残りの処理を飛ばして、末尾へ移動するという効果があります。末尾へ移動したあとで、ループの「条件式」を確認します。そのため、ループは継続するかもしれませんし、終了することになるかもしれません。

continue文の構文は単純です。while文、do文、for文の「繰り返したい文」のところに、以下のように書くだけです。

continue;

次のプログラムは、文字列の入力を受け取り、その内容が “exit” なら終了、"" なら何もせずループを続行、それ以外なら入力された文字列をそのまま出力するという動作を取ります。ループを終了させるために break文を、何もせずに続行させるために continue文を使っています。

#include <iostream>
#include <string>

int main()
{
    while (true) {
        std::cout << "Please enter the string.\n";
        std::string s {};
        std::getline(std::cin, s);

        if (s == "exit") {
            break;
        }
        if (s == "") {
            continue;
        }

        std::cout << s << "\n";
    }
}

実行結果:

Please enter the string.
abc  <-- 入力した文字列
abc
Please enter the string.
     <-- 入力した文字列(Enterキーを押しただけ)
Please enter the string.
exit  <-- 入力した文字列

空の文字列の入力を受け付けるために、std::getline関数を使う必要があります(「文字列の入力」のページを参照)。

if (s == "") のところは else を付けて、else if (s == "") としても構いませんが、事実上まったく同じ意味になることも理解しておきましょう。if (s == "exit") が true になった場合には break文が実行されるので、後続にある if (s == "") のところに来ることはないからです。ただ、どちらの書き方がいいとはっきりいえるほどのものでは無いと思います。

do文や for文での continue文の動作

while文での continue の挙動は想像しやすいと思いますが、do文や for文は少し迷うかもしれません。基本的には同じことで、残りの処理を飛ばして、「条件式」をチェックし、次の周回に入るか、ループを終了するかします。for文の場合は、++i などを書いている箇所の実行が挟まることに注意してください。

do文なら、以下の順番で実行されます。

  1. continue文が実行される
  2. 「条件式」を確認。true なら続行、false なら終了
  3. ループ本体の処理の先頭へ移動

for文の場合は、以下の順番で実行されます。

  1. continue文が実行される
  2. 「式」を実行(for (int i = 0; i < n; ++i) であれば ++i のこと)
  3. 「条件式」を確認。true なら続行、false なら終了
  4. ループ本体の処理の先頭へ移動

ループがネストしているとき

break文と同様、ループがネストしている場合に continue文の効果が及ぶのは、continue文が書かれているループだけです

次のプログラムは、1~9 の整数同士を掛け合わせた結果の表を出力しますが、答えが奇数になるものを取り除いています。

#include <iostream>

int main()
{
    for (int i = 1; i <= 9; ++i) {
        for (int j = 1; j <= 9; ++j) {
            int ans {i * j};

            if (ans % 2 != 0) {
                continue;  // 奇数だったら、残りの処理を飛ばして A へ移動
            }

            std::cout << ans << " ";

            // A (内側の for文の末尾)
        }
        std::cout << "\n";
    }
}

実行結果:

2 4 6 8
2 4 6 8 10 12 14 16 18
6 12 18 24
4 8 12 16 20 24 28 32 36
10 20 30 40
6 12 18 24 30 36 42 48 54
14 28 42 56
8 16 24 32 40 48 56 64 72
18 36 54 72

まとめ


新C++編の【本編】の各ページには、末尾に練習問題があります。ページ内で学んだ知識を確認する簡単な問題から、これまでに学んだ知識を組み合わせなければならない問題、あるいは更なる自力での調査や模索が必要になるような高難易度な問題をいくつか掲載しています。


参考リンク


練習問題

問題の難易度について。

★は、すべての方が取り組める入門レベルの問題です。
★★は、自力でプログラミングができるようなるために、入門者の方であっても取り組んでほしい問題です。
★★★は、本格的にプログラマーを目指す人のための問題です。

問題1 (確認★)

次の2つのコードの意味を説明してください。

if (n > 0) {
    std::cout << "OK.\n";
}
while (n > 0) {
    std::cout << "OK.\n";
}

解答・解説

問題2 (基本★)

次の while文はどういう意味ですか?

while (0) {
    // なんらかの処理
}

解答・解説

問題3 (基本★)

次の for文を、同じ結果になるように while文で書き換えてください。

for (int i = 0; i < 5; ++i) {
    std::cout << i << "\n";
}

解答・解説

問題4 (基本★★)

次の for文を、同じ結果になるように do文で書き換えてください。

for (int i = 0; i < 5; ++i) {
    std::cout << i << "\n";
}

解答・解説

問題5 (基本★★)

次の do文を、同じ結果になるように、無限ループを使ったかたちに書き換えてください。

#include <iostream>

int main()
{
    int value {};
    do {
        std::cout << "Please enter the integer.\n";
        std::cin >> value;

        if (value > 0) {
            std::cout << value << " is positive number.\n";
        }
        else if (value < 0) {
            std::cout << value << " is negative number.\n";
        }
    } while (value != 0);
}

解答・解説

問題6 (応用★★★)

末尾に .、途中にいくつかの空白(’ ’)を含んだ文字列があるとして、空白を詰めたものを出力するプログラムを、continue文を使って作成してください。

たとえは、“Hello, New World.” という文字列に対しては、“Hello,NewWorld.” を出力します。

解答・解説


解答・解説ページの先頭



更新履歴




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る