6

私は現在「Accelerated C++」に取り組んでおり、第3章でこれに出くわしました:

// invariant:
//  we have read count grades so far, and
//  sum is the sum of the first count grades
while (cin >> x) {
    ++count;
   sum  +=  x;
}

著者はこれに続いて、不変式には特別な注意を払う必要があると説明しています。これは、入力が に読み込まれると成績xが読み取られ、count + 1不変式が真でなくなるためです。同様に、カウンターをインクリメントするsumと、最後のカウント成績の合計ではなくなります (ご想像のとおり、これは学生の成績を計算するための従来のプログラムです)。

私が理解していないのは、なぜこれが重要なのかです。確かに、他のほぼすべてのループについて、同様のステートメントが当てはまりますか? たとえば、これは本の最初のwhileループです (出力は後で入力されます)。

// invariant: we have written r rows so far
while (r != rows) {
    // write a row of output 
    std::cout << std::endl;
    ++r;
}

r出力の適切な行を書き込んだら、他の例と同様に、をインクリメントするまで、不変条件は確実に false になりますか?

この 2 つの条件の違いは何ですか?

編集:すべての返信に感謝します。わかったと思いますが、念のために「承認された回答」を選択する前に、もう少しそのままにしておきます。これまでのところ、すべての回答は基本的に同意しているため、公正とは思えませんが、やる価値はあると思います。

以下に要求された元の段落:

「このループの不変条件を理解するには、特別な注意が必要です。なぜなら、while の条件には副作用があるからです。これらの副作用は、不変条件の真実に影響を与えます: cin >> x を正常に実行すると、不変条件の最初の部分、つまり、 count grades-false を読みました. したがって、条件自体が不変式に与える影響を説明するために、分析を変更する必要があります。

条件を評価する前に不変式が真であったことがわかっているので、すでにカウント グレードを読み取っていることがわかります。cin >> x が成功した場合、count + 1 の成績を読み取ったことになります。カウントをインクリメントすることで、不変式のこの部分を再び真にすることができます。ただし、これを行うと、不変式の 2 番目の部分 (合計は最初のカウントの成績の合計であるという部分) が改ざんされます。成績を数えます。幸いなことに、sum += x; を実行することで、不変式の 2 番目の部分を真にすることができます。そのため、その間の後続の旅行では不変式全体が真になります。

条件が false の場合、入力の試行が失敗したため、それ以上データを取得できなかったため、不変条件は依然として true であることを意味します。その結果、while が終了した後の状態の副作用を考慮する必要はありません。」

4

8 に答える 8

4

一般に、不変条件はループの反復間でのみ適用されると理解されています。(少なくとも私はそう読んでいます!)

一般的なケースは次のようになります。

[invariant true];
while (keep going) {
    [state transformation];
    [invariant true];
}

しかし、状態の変換中、不変条件は必ずしも成り立つとは限りません。

別のスタイル ノートとして、スーパー コーダーになりたい場合は、不変条件をコメントに残すのではなく、アサーションを作成してください。

// Loop invariant: x+y = -4
for (int x = 0; x < 10; x++) {
    [do something];
    assert(x+y == -4);  // Loop invariant here!
}

そうすれば、自己チェックコードが得られます。

于 2010-05-29T18:11:46.183 に答える
2

あなたの説明からすると、作者がナンセンスなことを言っているように聞こえます。はい、不変条件は命令間で一時的に正しくなくなりますが、このような非アトミック操作がある場合はいつでも発生します。不変式が正しくなく、プログラムが一貫性のない状態になる可能性のある明確なブレークポイントがない限り、問題はありません。

この場合、不変式が真でないときに std::cout が例外をスローすると、その例外をどこかでキャッチしても、悪い状態で実行を継続することが唯一の方法です。著者はあまりにも衒学的であると私には思えます。繰り返しますが、break/continue ステートメントが間違った場所にないか、例外がスローされていない限り、問題ありません。非常に単純なので、多くの人があなたのサンプルコードに集中することを気にしないと思います.

于 2010-05-29T18:07:20.857 に答える
2

この本は、while ループが停止する方法について言及していると思います。2 番目のケースでは、"r" が "rows" に等しくなるまでインクリメントされると、ループが停止することが非常に簡単にわかります。C++ のほとんどのカウントは 0 から始まるため、これは行ごとに 1 行を出力する可能性が高くなります。

一方、最初の例は、cin オブジェクトで ">>" の演算子オーバーロードを使用しています。この関数がゼロを返さない限り、while ループは継続します。その演算子のオーバーロードは、入力が閉じられるまでこの値を返しません。

「cin >>」が 0 を返すようにするには、どのキーを押すことができますか? そうしないと、ループが終了しません。そのようなループを作成しないようにする必要があります。

条件外でループを止める行を追加する必要があります。「break」と「continue」ステートメントを調べてください。

于 2010-05-29T18:11:07.123 に答える
2

これは、例外の安全性のコンテキストで非常に興味深い/重要です。

次のシナリオを検討してください。

  • 「カウント」は、オーバーロードされたユーザー定義クラスですoperator++
  • そのオーバーロードoperator++は例外をスローします。
  • 例外は後でループの外でキャッチされます (つまり、ループは現在のように見えます。try/catch はありません)。

その場合、ループの不変条件は成り立たなくなり、ループ内で行われているすべての状態が問題になります。行は書かれましたか?カウントは更新されましたか?合計はまだ正しいですか?

例外がスローされた場合でもすべての一貫性を維持するには、追加の保護 (中間値といくつかの try/catch を保持するための一時変数の形式) を使用する必要があります。

于 2010-05-29T18:14:12.707 に答える
1

私はあなたが不変量が何であるかを知らなければならないことに同意します。古き良きBankAccountを書いているとしましょう。常に、常に、トランザクション履歴内のすべてのトランザクションの合計がアカウントの残高に加算されると言えます。それは理にかなっているように聞こえます。それが真実であるのは良いことです。しかし、私がトランザクションを処理している数行の間、それは真実ではありません。最初に残高を更新するか、トランザクションを履歴に追加して残高を更新します。しばらくの間、不変条件は真実ではありません。

不変量は常に真実であると主張するものであると理解してほしい本を想像することができますが、そうでない場合もあり、真実でない場合は知っておくとよいでしょう。それが真実でない場合、戻るか、例外をスローするか、並行性に屈するか、または何でも、それからあなたのシステムはすべて台無しになります。あなたの言い換えが著者が意図したものであると想像するのは少し難しいです。しかし、頭を横に向けて目を細めれば、「合計は最初のカウントの成績の合計です」は、読んで追加するのに忙しいときを除いて、常に真実であると言い換えることができます。そうではないかもしれません。そのプロセス中に真。わかる?

于 2010-05-29T18:49:01.103 に答える
1

この本は、物事を本来よりもはるかに複雑にしているようです。不変条件でループを説明するのは良いことではないと思います。量子物理学で足し算を説明するようなものです。

著者は、入力が変数xに読み込まれると、count + 1グレードが読み込まれるため、不変条件が正​​しくないため、不変条件に特別な注意を払う必要があることを説明します。同様に、カウンターをインクリメントすると、変数の合計は最後のカウントの成績の合計ではなくなります(推測していなかった場合は、これは学生の点数を計算するための従来のプログラムです)。

まず第一に、不変条件は明確ではありません。while不変条件が「ループの反復の終わりにcount、合計がsum「」であるグレードを読み取った場合、それは私には正しいように見えます。不変条件は明確に指定されていないため、尊重されている場合と尊重されていない場合について話すことすら意味がありません。

不変条件が「ループの反復の任意の時点でwhile...」である場合、厳密に言えば、その不変条件は真ではありません。ループに関する限り、不変条件はループの開始点、終了点、または固定点に存在する状態。

私はその本を持っておらず、物事が明確になるかどうかはわかりませんが、不変条件を間違って使用しているようです。それらの不変条件が真でない場合、そもそもなぜ不変条件を使用することさえわざわざするのでしょうか。

これについてはあまり心配する必要はないと思います。これらのループがどのように機能するかを理解している限り、問題はありません。不変条件でそれらを理解したい場合は可能ですが、選択した不変条件に注意を払う必要があります。悪いものを選ばないでください、さもないと目的が果たせなくなります。ランダムなものを選ぶのではなく、それを尊重するコードを書くのが簡単な不変条件を選んでから、それを尊重するコードを書くのに苦労する必要があります。漠然としたものを選んで、それとは関係のないコードを書くのは間違いありません。そして、「これは私が選んだ漠然とした不変条件を実際には尊重しないので、注意を払う必要があります」と言います。

私が理解していないのは、なぜこれが重要なのかということです。確かに、他のループについても、同様のステートメントが当てはまりますか?

それは使用される不変条件に依存します(本はあなたが言ったことからそれについてかなり曖昧です)、しかしそうです、あなたはこの場合正しいようです。

このコードの場合:

// invariant: we have written r rows so far
int r = 0; // this is also important!
while (r != rows) {
    // write a row of output 
    std::cout << std::endl;
    ++r;
}

while不変条件「ループの反復の終わりに、r行を書き込んだ」は間違いなく真実です。

私はその本を持っていないので、これらすべてが後で取り上げられるかどうかはわかりません。あなたが言ったことから、これはループを説明するための本当にお粗末な方法のように思えます。

于 2010-05-29T18:23:37.680 に答える
1

あなたの更新に続いて、著者は、ループの反復ごとにループ不変条件をどのように「回復」する必要があるかを正しく説明しています。

私が理解していないのは、なぜこれが重要なのかです。確かに、他のほぼすべてのループについて、同様のステートメントが当てはまりますか?

はい、そのとおりです。このループには特別なことは何もありません (OK、ループ条件には副作用がありますが、これは簡単に書き直すことができます)。

しかし、著者が指摘したかった重要な事実は次のとおりだと思います: ループ内でアクションが実行された後、ループの不変条件は一般的にもはや真ではなくなります。もちろん、これは、後続のステートメントが適切なアクションを実行してこれを修正しない限り、不変条件の問題です。

于 2010-05-29T19:30:46.243 に答える
0

最初の例では、カウント変数は、入力ループごとにインクリメントされる以外の目的では使用されていません。ループは >> が NULL を返すまで続きます。

2 番目の例では、行を書き込む行数で初期化する必要があります。指定された行数が書き込まれるまでループが続きます。

while (cin >> x) {
    ++count;
}


rows = NROWS;
r = 0;

while (r != rows) {
    // write a row of output 
    std::cout << std::endl;
    ++r;
}
于 2010-05-29T23:22:46.067 に答える