web-dev-qa-db-ja.com

「for」ループで1をインクリメントするときに、!=の代わりに>(<)を使用する技術的な理由はありますか?

このようなforループはほとんど見られません。

for (int i = 0; 5 != i; ++i)
{}

forループで1ずつインクリメントするときに、>の代わりに<または!=を使用する技術的な理由はありますか?または、これは慣習の詳細ですか?

197
Zingam
while (time != 6:30pm) {
    Work();
}

それは午後6時31分です...くそー、今家に帰る私の次のチャンスは明日です! :)

これは、より強い制限がリスクを軽減し、おそらくより直感的に理解できることを示しています。

301
Antonio

技術的な理由はありません。しかし、リスクの軽減、保守性、およびコードのより良い理解があります。

<または>!=よりも強い制限であり、ほとんどの場合にまったく同じ目的を果たします(すべての実用的な場合でも言います)。

重複した質問があります here ;そして、1つの興味深い answer

94
Ely

はい、理由があります。このようなforループ(単純な古いインデックスベース)を記述する場合

for (int i = a; i < b; ++i){}

aおよびbの値(つまり、a > bを使用した場合、無限ではなくi == b;の場合はゼロの繰り返し)に対して期待どおりに動作します。

一方、イテレータの場合は次のように記述します

for (auto it = begin; it != end; ++it) 

イテレータはoperator!=を実装する必要がありますが、すべてのイテレータに対してではなく、operator<を提供することができます。

範囲ベースのforループも

for (auto e : v)

ただの砂糖ではありませんが、間違ったコードを書く可能性をかなり減らします。

あなたのようなものを持つことができます

for(int i = 0; i<5; ++i){
    ...
    if(...) i++;
    ...
}

ループ変数が内部コードによって記述されている場合、i!=5はそのループを中断しない可能性があります。不平等をチェックする方が安全です。

編集読みやすさについて。不等式形式は、より頻繁に使用されます。したがって、理解する特別なものはないため、これは非常に高速に読み取れます(タスクが一般的であるため、脳の負荷が軽減されます)。したがって、読者がこれらの習慣を利用するのはクールです。

69
johan d.

そして最後になりましたが、これはdefensive programmingと呼ばれます。これは、プログラムに影響を与える現在および将来のエラーを回避するために、常に最強のケースをとることを意味します。

防御的プログラミングが不要な唯一のケースは、状態が事前条件と事後条件によって証明されている場合です(ただし、これがすべてのプログラミングの中で最も防御的であることを証明します)。

48
Paul Ogilvie

私は次のような表現を主張します

for ( int i = 0 ; i < 100 ; ++i )
{
  ...
}

より多い意図を表現する

for ( int i = 0 ; i != 100 ; ++i )
{
  ...
}

前者は、条件が範囲の排他的上限のテストであることを明確に示しています。後者は、終了条件のバイナリテストです。また、ループの本文が重要でない場合、インデックスがforステートメント自体でのみ変更されることは明らかではありません。

37
Nicholas Carey

イテレータは、!=表記を最も頻繁に使用する場合の重要なケースです。

for(auto it = vector.begin(); it != vector.end(); ++it) {
 // do stuff
}

確かに、実際にはrange-forに依存して同じことを書くでしょう:

for(auto & item : vector) {
 // do stuff
}

ただし、ポイントは残ります。通常、==または!=を使用してイテレータを比較します。

21
Escualo

ループ条件は、強制されたループ不変条件です。

ループの本体を見ていないと仮定します。

for (int i = 0; i != 5; ++i)
{
  // ?
}

この場合、ループの反復の開始時に、i5と等しくないことがわかります。

for (int i = 0; i < 5; ++i)
{
  // ?
}

この場合、ループの繰り返しの開始時に、i5より小さいことがわかります。

2番目は1番目よりもはるかに多くの情報です。プログラマーの意図は(ほぼ確実に)同じですが、バグを探しているなら、コード行を読むことに自信があるのは良いことです。そして、2番目のenforcesその不変式は、2番目のケースでは発生しない(またはメモリの破損を引き起こさないなど)最初のケースでは噛みつくバグがいくつかあることを意味します。

<を使用するよりも!=を使用する方がコードの読み取りが少ないため、プログラムの状態について詳しく知ることができます。また、最新のCPUでは、差がないのと同じ時間がかかります。

iがループ本体で操作されなかった場合、andは常に1ずつ増加します-and5未満で開始され、違いはありません。しかし、それが操作されたかどうかを知るには、これらの各事実を確認する必要があります。

これらの事実のいくつかは比較的簡単ですが、間違っている可能性があります。ただし、ループ全体をチェックするのは苦痛です。

C++では、次のようなindexes型を記述できます。

for( const int i : indexes(0, 5) )
{
  // ?
}

上記の2つのforループのいずれかと同じことを行います。コンパイラーが同じコードに最適化するまでです。ただし、ここでは、コードがメモリを破損しない限り、iが宣言されているため、ループの本体でconstを操作できないことをknowします。

コンテキストを理解しなくてもコード行から情報を取得できるほど、問題の原因を簡単に追跡できます。整数ループの場合の<は、!=よりも、その行のコードの状態に関する詳細情報を提供します。

変数iが大きな値に設定されている場合があり、!=演算子を使用すると、無限ループに陥ることがあります。

13
MosteM

はい; OpenMPは、!=条件でループを並列化しません。

12
Mehrdad

Ian Newsonが既に言ったように、浮動変数を確実にループして!=で終了することはできません。例えば、

for (double x=0; x!=1; x+=0.1) {}

0.1は浮動小数点で正確に表現できないため、実際には永遠にループします。したがって、カウンターは1をわずかにミスします。<で終了します。

(ただし、基本的にはndefined behaviour最後に受け入れられた数値として0.9999 ...を取得するかどうか(これはより小さい前提に違反するかどうか)、またはすでに1.0000000000000001で終了することに注意してください。)

12
leftaroundabout

他の多数の回答からわかるように、!=の代わりに<を使用する理由があります。これは、Edgeのケース、初期条件、意図しないループカウンターの変更などに役立ちます。

正直なところ、コンベンションの重要性を十分に強調できるとは思いません。この例では、他のプログラマーがあなたが何をしようとしているのかを見るのは十分簡単ですが、それはダブルテイクを引き起こします。プログラミング中の仕事の1つは、できるだけ読みやすく親しみやすいものにすることです。そのため、誰かがコードを更新/変更する必要がある場合、必然的に、異なるコードブロックで何をしていたかを把握するのに多くの労力はかかりません。誰かが!=を使用しているのを見た場合、<の代わりにそれを使用した理由があると思います。それが大きなループである場合は、全体を調べて、それはそれを必要としました...そしてそれは時間の無駄です。

12
RyanP

私は形容詞「技術的」を、言語の動作/癖、および生成されたコードのパフォーマンスなどのコンパイラーの副作用を意味します。

このため、答えはno(*)です。 (*)は「プロセッサのマニュアルを参照してください」です。エッジケースRISCまたはFPGAシステムを使用している場合は、生成される命令とそのコストを確認する必要があります。ただし、従来の現代的なアーキテクチャをほとんど使用している場合、lteqne、およびgtの間にプロセッサレベルのコストに大きな違いはありません。

If Edgeケースを使用している場合、!=には3つの操作(cmpnotbeq)と2つの操作(cmpblt xtr myo)。この場合も、RTMです。

ほとんどの場合、その理由は、特にポインターまたは複雑なループを使用する場合、防御/強化です。検討する

// highly contrived example
size_t count_chars(char c, const char* str, size_t len) {
    size_t count = 0;
    bool quoted = false;
    const char* p = str;
    while (p != str + len) {
        if (*p == '"') {
            quote = !quote;
            ++p;
        }
        if (*(p++) == c && !quoted)
            ++count;
    }
    return count;
}

あまり考えられない例は、戻り値を使用して増分を実行し、ユーザーからデータを受け入れる場合です。

#include <iostream>
int main() {
    size_t len = 5, step;
    for (size_t i = 0; i != len; ) {
        std::cout << "i = " << i << ", step? " << std::flush;

        std::cin >> step;
        i += step; // here for emphasis, it could go in the for(;;)
    }
}

これを試して、値1、2、10、999を入力します。

これを防ぐことができます:

#include <iostream>
int main() {
    size_t len = 5, step;
    for (size_t i = 0; i != len; ) {
        std::cout << "i = " << i << ", step? " << std::flush;
        std::cin >> step;
        if (step + i > len)
            std::cout << "too much.\n";
        else
            i += step;
    }
}

しかし、おそらくあなたが望んでいたのは

#include <iostream>
int main() {
    size_t len = 5, step;
    for (size_t i = 0; i < len; ) {
        std::cout << "i = " << i << ", step? " << std::flush;
        std::cin >> step;
        i += step;
    }
}

標準コンテナでの順序付けは、多くの場合<に依存するため、operator<への慣習的なバイアスもあります。たとえば、いくつかのSTLコンテナでのハッシュは、

if (lhs < rhs) // T.operator <
    lessthan
else if (rhs < lhs) // T.operator < again
    greaterthan
else
    equal

lhsおよびrhsが、このコードを次のように記述するユーザー定義クラスである場合

if (lhs < rhs) // requires T.operator<
    lessthan
else if (lhs > rhs) // requires T.operator>
    greaterthan
else
    equal

実装者は2つの比較関数を提供する必要があります。したがって、<が好まれる演算子になりました。

10
kfsone

任意の種類のコードを記述する方法はいくつかありますが(通常)、この場合はたまたま2つの方法があります(<=および> =を数える場合は3つ)。

この場合、人々は>と<を好んで、ループ内で予期しない何か(バグなど)が発生した場合でも、無限ループ(BAD)にならないようにします。たとえば、次のコードを検討してください。

for (int i = 1; i != 3; i++) {
    //More Code
    i = 5; //OOPS! MISTAKE!
    //More Code
}

(i <3)を使用した場合、より大きな制限が課されるため、無限ループから安全になります。

プログラムの間違いですべてをシャットダウンするか、そこでバグを機能させ続けるかは、本当にあなたの選択です。

これが役に立てば幸いです!

<を使用する最も一般的な理由は、慣習です。多くのプログラマーは、このようなループを「インデックスが最後に達するまで」ではなく「インデックスが範囲内にある」と考えています。できれば慣習にこだわる価値があります。

一方、ここでの多くの回答は、<フォームを使用するとバグを回避できると主張しています。多くの場合、これは単にhideバグに役立つと主張します。ループインデックスが終了値に到達することになっていて、代わりに実際にそれを超える場合、誤動作(または別のバグの副作用)を引き起こす可能性がある、予期しない何かが発生します。 <は、バグの発見を遅らせる可能性があります。 !=は、ストール、ハング、またはクラッシュに至る可能性が高く、バグをより早く発見するのに役立ちます。バグがすぐに見つかると、修正のコストは安くなります。

この規則は、配列およびベクトルのインデックス付けに固有のものであることに注意してください。他のほぼすべてのタイプのデータ構造をトラバースする場合、イテレーター(またはポインター)を使用して、終了値を直接チェックします。これらの場合、イテレーターが実際の最終値を超えないようにしなければなりません。

たとえば、プレーンなC文字列をステップスルーする場合、一般的に次のように記述するのが一般的です。

for (char *p = foo; *p != '\0'; ++p) {
  // do something with *p
}

より

int length = strlen(foo);
for (int i = 0; i < length; ++i) {
  // do something with foo[i]
}

1つには、文字列が非常に長い場合、strlenは文字列を通過する別のパスであるため、2番目の形式は遅くなります。

C++ std :: stringを使用すると、長さがすぐに利用できる場合でも、範囲ベースのforループ、標準アルゴリズム、または反復子を使用します。イテレータを使用している場合、次のように、!=ではなく<を使用するのが慣例です。

for (auto it = foo.begin(); it != foo.end(); ++it) { ... }

同様に、ツリー、リスト、または両端キューを反復するには、通常、インデックスが範囲内に残っているかどうかをチェックするのではなく、nullポインターまたは他のセンチネルを監視する必要があります。

7
Adrian McCarthy

プログラミング言語は、結局のところ、(とりわけ)人間によって読まれる言語であるという事実に関係しているこのプラクティスに従うための2つの関連した理由があります。

(1)冗長性のビット。自然言語では、通常、エラー修正コードのように、厳密に必要な情報よりも多くの情報を提供します。ここで追加情報は、ループ変数i(ここで冗長性の使用方法を参照してください。「ループ変数」の意味がわからない場合、または「ループ変数」を読んだ後に変数の名前を忘れた場合i "完全な情報があります)は、ループ中に5未満であり、5とは異なりません。冗長性は読みやすさを向上させます。

(2)コンベンション。言語には、特定の状況を表現する特定の標準的な方法があります。確立された言い方に従わない場合でも理解できますが、特定の最適化が機能しないため、メッセージの受信者の努力は大きくなります。例:

熱いマッシュの周りで話さないでください。難易度を照らすだけです!

最初の文は、ドイツ語のイディオムの文字通りの翻訳です。 2番目は、主要な単語を同義語に置き換えた一般的な英語のイディオムです。結果はわかりやすいですが、これよりも理解するのに時間がかかります。

茂みの周りを叩かないでください。問題を説明してください!

これは、最初のバージョンで使用されている同義語がたまたま英語のイディオムの従来の単語よりも状況に合っている場合でも当てはまります。プログラマーがコードを読み取るときに、同様の力が有効になります。これは、5 != i5 > iがそれを置く奇妙な方法である理由でもあります、それが存在する環境で作業している場合を除きますこの方法で、より通常のi != 5i < 5を交換する標準。このような方言コミュニティは存在します。おそらく一貫性により、自然だがエラーを起こしやすい5 == iの代わりにi == 5を書くことを覚えやすくなるからです。

7
Hans Adler

この構成を使用しない理由の1つは、浮動小数点数です。 !=は、数値が同じに見えてもtrueと評価されることはめったにないため、floatで使用するのは非常に危険な比較です。 <または>は、このリスクを取り除きます。

7
Ian Newson

そのような場合にリレーショナル比較を使用することは、他の何よりも一般的な習慣です。イテレータのカテゴリやその比較可能性などの概念的な考慮事項が優先度の高いものと見なされなかった時代に、その人気が戻ってきました。

等値比較は比較される値により少ない要件を課すため、可能な場合は関係比較の代わりに等値比較を使用することを好むべきだと思います。 EqualityComparableであることは、LessThanComparableであるよりも要件が少なくなります。

このようなコンテキストでの等値比較のより広い適用性を示す別の例は、unsigned反復を0まで実装することに関する一般的な難問です。として行うことができます

for (unsigned i = 42; i != -1; --i)
  ...

上記は符号付きおよび符号なしの両方の反復に等しく適用できますが、リレーショナルバージョンは符号なしの型で分類されることに注意してください。

6
AnT

ループ変数が(意図しない)本体内で変化する例に加えて、より小さい演算子またはより大きい演算子を使用する他の理由があります。

  • 否定によりコードが理解しにくくなります
  • <または>は1文字のみですが、!=は2文字です
2
MiCo

リスクを軽減すると述べたさまざまな人々に加えて、さまざまな標準ライブラリコンポーネントと対話するために必要な関数のオーバーロードの数も減らします。たとえば、タイプをstd::setに格納可能にする場合、またはstd::mapのキーとして使用する場合、または検索およびソートアルゴリズムの一部で使用する場合、標準ライブラリは通常std::lessほとんどのアルゴリズムは厳密な弱い順序付けのみを必要とするため、オブジェクトを比較します。したがって、<比較の代わりに!=比較を使用するのが良い習慣になります(もちろん理にかなっている場合)。

1
Andre Kostur

構文の観点からは問題ありませんが、その式5!=iの背後にあるロジックは適切ではありません。

私の意見では、!=を使用してforループの境界を設定することは論理的に適切ではありません。なぜなら、forループは反復インデックスをインクリメントまたはデクリメントするため、反復インデックスが境界外になるまでループを設定する(!=に何か)適切な実装ではありません。

それは機能しますが、増分問題に!=を使用すると境界データの処理が失われるため、誤動作する傾向があります(つまり、最初からインクリメントまたはデクリメントしていることがわかります)。そのため、!=の代わりに<>>==>が使用されます。

0
Cosmin Gherghel