web-dev-qa-db-ja.com

成功するまで繰り返し、失敗を処理するループを構築する方法

私は独学のプログラマーです。私は約1.5年前にプログラミングを始めました。今、私は学校でプログラミングクラスを始めました。プログラミングクラスは1/2年間ありましたが、今はさらに1/2になります。

クラスでは、C++でプログラミングすることを学習しています(これは、始める前に、私がすでにかなりよく使用することを知っていた言語です)。

このクラスでは何の問題もありませんでしたが、明確な解決策を見つけることができなかった問題が繰り返し発生しています。

問題は次のようになります(疑似コード):

_ do something
 if something failed:
     handle the error
     try something (line 1) again
 else:
     we are done!
_

これはC++の例です

このコードはユーザーに数値の入力を求め、入力が有効になるまで入力を続けます。 cin.fail()を使用して、入力が無効かどうかを確認します。 cin.fail()trueの場合、cin.clear()およびcin.ignore()を呼び出して、ストリームから引き続き入力を取得できるようにする必要があります。

このコードはEOFをチェックしないことを知っています。私たちが書いたプログラムは、そうすることを期待されていません。

これが私が学校での課題の1つでコードを書いた方法です。

_for (;;) {
    cout << ": ";
    cin >> input;
    if (cin.fail()) {
        cin.clear();
        cin.ignore(512, '\n');
        continue;
    }
    break;
}
_

私の先生は、私はbreakcontinueをこのように使うべきではないと言っていました。彼は、代わりに通常のwhileまたは_do ... while_ループを使用するように提案しました。

breakcontinueを使用することが、この種のループを表す最も簡単な方法であるように私には思えます。私は実際にそれについてかなり長い間考えましたが、より明確な解決策を実際には思い付きませんでした。

彼は私に次のようなことをして欲しかったと思います:

_do {
    cout << ": ";
    cin >> input;
    bool fail = cin.fail();
    if (fail) {
        cin.clear();
        cin.ignore(512, '\n');
    }
} while (fail);
_

私にとって、このバージョンは追跡するためのfailと呼ばれる変数もあり、入力エラーのチェックが一度だけではなくtwiceで行われるため、かなり複雑に思えます。

また、次のようなコードを記述できることもわかりました(短絡評価を悪用しています)。

_do {
    cout << ": ";
    cin >> input;
    if (fail) {
        cin.clear();
        cin.ignore(512, '\n');
    }
} while (cin.fail() && (cin.clear(), cin.ignore(512, '\n', true);
_

このバージョンは他のバージョンとまったく同じように機能します。 breakまたはcontinueを使用せず、cin.fail()テストは1回だけ実行されます。しかし、このように「短絡評価ルール」を乱用するのは私には適切ではないようです。私の先生もそれを望んでいないと思います。

この問題は、cin.fail()チェックだけに当てはまるわけではありません。私はbreakcontinueを他の多くの場合に使用しましたが、条件が満たされるまでコードセットを繰り返す必要があり、条件が満たされない場合も何かを実行する必要があります(呼び出しなど) cin.clear()cin.ignore(...)およびcin.fail()の例)。

私はコース全体でbreakcontinueを使い続けてきましたが、私の教師はこれについて不平を言うのをやめました。

これについてのあなたの意見は何ですか?

私の先生は正しいと思いますか?

この種の問題を表すより良い方法を知っていますか?

8
wefwefa3

Ifステートメントは少し異なるので、入力が成功したときに取得します。

for (;;) {
    cout << ": ";
    if (cin >> input)
        break;
    cin.clear();
    cin.ignore(512, '\n');
}

それも短いです。

先生が好むかもしれない短い方法を示唆しています:

cout << ": ";
while (!(cin >> input)) {
    cin.clear();
    cin.ignore(512, '\n');
    cout << ": ";
}
15
Sjoerd

あなたが努力しなければならないのは、 生のループを回避する です。

複雑なロジックをヘルパー関数に移動すると、突然物事がより明確になります。

bool getValidUserInput(string & input)
{
    cout << ": ";
    cin >> input;
    if (cin.fail()) {
        cin.clear();
        cin.ignore(512, '\n');
        return false;
    }
    return true;
}

int main() {
    string input;
    while (!getValidUserInput(input)) { 
        // We wait for a valid user input...
    }
}
15
glampert

for(;;)が悪いのはそれほど多くありません。次のようなパターンほど明確ではありません。

while (cin.fail()) {
    ...
}

またはSjoerdが言ったように:

while (!(cin >> input)) {
    ...
}

このようなものの主な対象者は、あなたが仲間のプログラマーであると考えてみましょう。そのようにして最後に休憩をとった理由を覚えていない、または継続して休憩を飛び越えた自分の将来のバージョンを含みます。あなたの例からのこのパターン:

for (;;) {
    ...
    if (blah) {
        continue;
    }
    break;
}

...他のパターンと比較してシミュレーションするには、数秒の追加の思考が必要です。これは難しい規則ではありませんが、continueを使用してbreakステートメントをジャンプするのは面倒で、役に立たないので賢く、ループの最後にbreakステートメントを配置することは、たとえそれが機能したとしても、異常です。通常、breakcontinueの両方を使用して時期尚早ループまたはループの反復を回避します。そのため、最後にそれらのいずれかを見ると、少し奇妙に感じられます。そうではありません。

それ以外に、良いもの!

9
jdevlin

学校のロジックインストラクターはいつも、ループに入る入り口と出口は1つだけあるべきだと言って、それを脳に打ち込みました。そうでない場合は、スパゲッティコードの取得を開始し、デバッグ中にコードがどこに行くのかを知りません。

現実には、コードの読みやすさが非常に重要であるため、他の誰かがコードの問題を修正またはデバッグする必要がある場合でも、簡単に実行できるようになっています。

また、この外観を何百万回も実行しているためパフォーマンスの問題である場合は、forループの方が高速かもしれません。テストはその質問に答えます。

C++は知りませんが、コード例の最初の2つは完全に理解しました。 continueがforループの終わりに行き、それを再びループすると仮定しています。 whileの例は完全に理解しましたが、for(;;)の例よりも理解しやすかったです。 3つ目は、それを理解するためにいくつかの思考作業を行わなければならないでしょう。

読みやすかった(c ++がわからない)私と、ロジックインストラクターがwhileループ1を使用すると言っていたことを説明します。

3
Jaydel Gluckie