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


198

私はforこのようなループを見ることはほとんどありません:

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

ループで1ずつインクリメントするとき、>または<その代わりに使用する技術的な理由はありますか?それとももっと慣例なのでしょうか?!=for


104
これにより読みやすくなり、さらに(たとえば)に変更i++した場合i+=2、非常に長時間(または場合によっては永久に)実行されます。さて、通常は<、イテレータを1 以上インクリメントする場合に使用するので、イテレータを1 <ずつインクリメントする場合にも使用できます(整合性のため)。
barak manos 2015

112
5 != iis evil
Slava

27
あなたは常に自分がやっていることを意識する必要がありますが、とにかく間違いを犯します。<を使用すると、このコードにバグが発生する可能性が低くなります。
オーラスト2015

40
@OleTangeあなたがすでにねじ込まれる浮動小数点数を使用して反復している場合...
CaptainCodeman

29
@スラバ...悪じゃない。あなたは明らかに間の違いを作るタイプミス、簡単なことでCのビットを行ったことがないif ( i == 5 ) ...if ( i = 5 )。非常に異なるもの。オペランドを逆にすることで問題を防ぐことができます。リテラルはでないためlvals、代入することができず、コンパイラーはタイプミスを投げかけます。@Zingam:防御的なプログラミング!
ニコラスキャリー

回答:


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

午後6時31分です。いまいましい、次の家に帰るチャンスは明日です。:)

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


25
「リスクを軽減する」ことを試みても、バグが隠れてしまうことがあります。ループの設計が午後6時30分に気付かれずにすべらないようにする必要がある場合、使用<するとバグが非表示になりますが、使用!=すると問題があることが明らかになります。
エイドリアンマッカーシー

27
@AdrianMcCarthy:必ずしもそうではありません。2つ目のバグが発生し、診断が難しくなる可能性があります。
オービットの軽量レース、2015

4
@AdrianMcCarthy:それは私の言いたいことです。まだ見たことのないインスタンスがいくつかあるかもしれません;)
オービットのライトネスレース

6
イテレータは、@ AdrianMcCarthyポイントの完璧な例だと思います。それらの推奨される比較は、<ではなく!=です。
Taekahn、2015

5
私はまさにこのエラーを見ました(そしてそれは3か月のデバッグを引き起こしました)-しかし、この答えは完全に要点を逃しています。与えられた例では、終了条件を見逃すことは不可能です。意識的に!=よりも<を選択することは、この事実を強く主張することであるとも言えます。確かに存在しないリスクを軽減することは、コードの匂いです。(そうは言っても、<が慣用的であるということは、同じようにそれを使用する理由として適切であると主張することができます。)
Ian Goldby

95

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

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

ここに重複する質問があります。と1つの興味深い答え


6
イテレータなど、<または>をサポートしないが==および!=をサポートするタイプがあります。
Simon B

1
これはintの forループです。イテレータはありません。
2015

7
「しかし、リスクの軽減、保守性、およびコードのより良い理解があります。」これらはすべて、技術的な理由によるものです。:-)
TJクラウダー2015

@TJCrowder結果としてマシンが何をするかについて話したいだけの理由は何ですか?
ブリリアント、

1
@DanSheppard私は通常、リスクの軽減と保守性をビジネス上の理由と見なし、コードの理解を深めることは社会的な理由と見なします(これらの理由はすべて、この場合の技術的な問題に適用されています)。「リスクの軽減」に関する考慮事項は、技術的な問題に限定されるものではなく、「コードのより良い理解」に関する考慮事項は、異なるグループの人々と作業している場合(関係するマシンがまったく変わらない場合でも)変わる可能性があります。 )。
ブリリアント、

85

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

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

その後、それはaandのすべての値に対して期待どおりに機能しますb(つまりa > b、を使用した場合、無限ではなくゼロの反復i == b;)。

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

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

どのイテレータもを実装する必要operator!=がありますが、すべてのイテレータに対してではないため、を提供することが可能operator<です。

また、範囲ベースのforループ

for (auto e : v)

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


4
いい例です。の!=代わりにのケースに興味があり<ます。個人的には思っていたものを使ったことはありません。
2015

@Elyasin私は良いものを見つけられず、悪いものを投稿したくありませんでした。ある種の円形のコンテナーがあり、コンテナーNのサイズを+ xモジュロ増加させ、ループが発生したときに停止したいとします。特定のインデックスに到達する....まあそれは本当に悪い例です。多分私はより良いものを見つけるでしょう
idclev 463035818

同じループで<の代わりに!=を使用すると、このコンテキストでの<(または>)の利点が明確になります。for (int i=a; i != b; i++) {}
ポールスミス

10
!=イテレータ以外のものを使用することは非常に危険です。イテレータは比較よりも少ない場合があります(たとえば、ポインタです)が、比較が意味をなさない場合があります(リンクリストです)。
Zan Lynx

1
真剣にホワイトスペースの使い方を学びましょう。
Miles Rout 2015

70

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

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

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

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


6
このタイプの「if(条件)then i ++」は、私が最初に考えたものです。
WernerCD、2015

3
これが最も高い投票された答えであると期待して来ました。私はそれを見つけるためにここまでスクロールしなければならないことに驚きました。これは、「まあ、あなたは最強の条件を使うべきだ」というよりも、初心者プログラマーを説得するために多くのことを行います。他の回答がトップにとどまる場合は、これを、OPが提案するコードで何が問題になるかについての明確で明白な説明として組み込む必要があります。
msouth 2015

1
@msouth私は完全に同意しますが、私のPOVはかなり主観的です;)
johan d '17

3
@msouth予期しない本番の動作でバグが明らかになったとき、私はそれを気に入っていますね。:-)
deworde 2015

3
@msouth見て、私たちがしなければならないことは、決して間違いを犯すことはなく、すべてがうまくいくでしょう。シンプル。
deworde 2015

48

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

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


2
これをサポートする1​​つの良い例:メモリは、放射線によって引き起こされるビットフリップ(SEU)に対して脆弱です。使用しているときintのためにi != 15条件4ビット目以上のものが特にマシンに、反転された場合、カウンタは、長時間実行されるsizeof(int)64のSEUは、より高い高度で又は空間内の非常に現実的な懸念であるである(なぜなら、より高い放射線の) 、または大規模なスーパーコンピュータ(大量のメモリが存在するため)。
Josh Sanford

2
放射線があなたの記憶を変えているときにあなたのプログラムが大丈夫だと考えることは楽観的な方法であり、破滅的な方法です...良いコメント!:)
Luis Colorado

37

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

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

以上で意思の表現であるより

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

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


13
「このループを最大で5回実行したい」と「このループを必要なだけ実行したいが、正確に5回だけ実行したいが、それは望ましくない」
ビショップ

範囲の上限について言及する場合は+1。これは数値範囲にとって重要だと思います。!=は、次のようなforループで適切な範囲を定義していません
element11

22

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

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

承知しました:実際には、私はに依存して同じことを書きますrange-for

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

ただし、要点は残っています。通常は、==またはを使用してイテレータを比較します!=


2
多くの場合、イテレータには平等/不平等の概念のみがあり、順序付けはありません!=。そのため、イテレータを使用します。たとえば、中にstd::vectorあなたは可能性が使用<反復子はインデックス/ポインタに結びついているとして、しかし、でstd::set、それは、バイナリツリーを歩くように、そのような厳密な順序付けはありません。
マーティンC.

19

ループ条件は強制ループ不変です。

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

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

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

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

この場合には、あなたはループ反復の開始時に知っているiよりも少ないです5

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

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

あなたは場合はiループ本体で操作されなかった、そしてそれは常に1増加した、そしてそれは以下の開始しました5、何の違いもないでしょう。しかし、それが操作されたかどうかを知るためには、これらの各事実を確認する必要があります。

これらの事実のいくつかは比較的簡単ですが、間違える可能性があります。ただし、ループ全体を確認するのは面倒です。

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

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

上記の2つのforループのいずれかと同じことを行います。コンパイラーが同じコードに最適化するまでです。ただし、ここでは、コードがメモリを破壊することなく、宣言されているようにループの本体で操作できないことを知っiconstます。

コンテキストを理解しなくてもコード行から得られる情報が多いほど、問題の原因を追跡しやすくなります。 <整数ループの場合は、その行のコードの状態についてよりも多くの情報を提供します!=



13

変数iがいくつかの大きな値に設定されている可能性があり、!=演算子を使用するだけの場合は、無限ループに陥ります。


1
本当に無限ではありません-Cのほとんどの実装でiは、最大になったときに黙ってラップアラウンドします。しかし、ええ、おそらくあなたが待つ準備ができているよりも長くかかります。
usr2564301 2015

12

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

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


12

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

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

0.1は浮動小数点で正確に表すことができないため、実際には永久にループします。したがって、カウンターは1をわずかに見逃します<

(ただし、最後に受け入れられた数値として0.9999 ...が得られるかどうかは、基本的に定義されていない動作であることに注意してください。


10

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

そのための答えは、no(*)です。(*)は「プロセッサーのマニュアルを参照してください」です。エッジケースのRISCまたはFPGAシステムを使用している場合は、生成される命令とそのコストを確認する必要がある場合があります。あなたはかなり任意の従来の近代建築を使用している場合でも、そこの間にコストの有意なプロセッサレベル差はありませんlteqnegt

場合あなたがエッジケースを使用している、あなたはそれが見つけることができる!=3つの操作(必要としcmpnotbeq2(VS)を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つの比較関数を提供する必要があります。それで<、支持されたオペレーターになりました。


読みやすくするために、i + =ステップはfor制御部分にある必要があります。ステップ= 0の場合
Mikkel Christiansen

1
それは良いコードの可読性を助けるだろうが、私は間違ったバージョンで欠陥を強調したかった
kfsone

9

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

この場合、(バグのように)ループで予期しないことが起こっても無限にループしないようにするために、>と<が好まれます(BAD)。たとえば、次のコードを考えます。

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

(i <3)を使用すると、制限が大きくなるため、無限ループから安全になります。

プログラムでミスをして全体をシャットダウンしたり、バグのある場所で機能し続けたりするのは、本当にあなたの選択です。

これが役に立てば幸い!


無限ループは間違いの良い指標であると主張する人もいますが、別の人は、i = 5は必ずしも間違いではないと主張します
njzk2

7

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

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

この規則は配列とベクトルのインデックス付けに固有であることに注意してください。他のほぼすべてのタイプのデータ構造をトラバースするときは、イテレーター(またはポインター)を使用して、終了値を直接確認します。これらの場合、イテレータが実際の最終値に到達し、オーバーシュートしないようにする必要があります。

たとえば、プレーンな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つには、文字列が非常に長い場合、2番目の形式はstrlen文字列を通過するもう1つのパスであるため、速度が遅くなります。

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

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

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


3
プログラミングにおけるイディオムの重要性を強調するのはまったく正しい。いくつかのコードを見て、それがおなじみのパターンを持っている場合、パターンを推測する時間を無駄にする必要はありませんが、そのコアに直接進むことができます。それがヨーダの比較での私の主なグリップだと思います-それらは慣用的に見えないので、私がそれらを私が思う意味を確実に意味することを確認するためにそれらを2回読む必要があります。
Toby Speight、2015

7

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


ループイテレータが浮動小数点数である場合、!=vs <が問題の最小です。
ランディン2017

7

この慣習に従う理由は2つあります。どちらも、プログラミング言語は結局のところ、(とりわけ)人間によって読まれる言語であるという事実に関係しています。

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

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

ホットマッシュについて話さないでください。難しさを明らかにするだけです!

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

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

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


1
Ausgezeichnet。同様に、テストを次のように記述しませんi < 17+(36/-3)(これらが他で使用されている定数である場合でも、わかりやすくするために名前を書き出す必要があります!)。
usr2564301 2015

6

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

等価比較は比較される値に課せられる要件が少ないため、可能な場合は常にリレーショナル比較ではなく等価比較を使用することをお勧めします。ビーイングEqualityComparableがあることよりも低い要件ですLessThanComparable

このようなコンテキストでの同等性比較の幅広い適用性を示す別の例は、unsignedまでの反復を実装することでよくある難問0です。それは次のように行うことができます

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

上記は、署名付きと符号なしの両方の反復に等しく適用できますが、リレーショナルバージョンは、符号なしの型に分類されます。


2

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

  • 否定はコードを理解しにくくします
  • <または>1文字だけですが、!=2

4
2番目の弾丸はせいぜい軽薄な理由です。コードは正しく明確でなければなりません。コンパクトはそれほど重要ではありません。
ジョナサンレフラー、2015

@JonathanLefflerさて、TDD REPLでは、その余分な文字は解析に1ナノ秒余分であり、書き込みによって引き起こされたバグを見つけるための10億回の反復の1倍の時間5 != $iでした。ええと... スニッカー
ビショップ

1

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


1
オーバーロードの数は正しいかもしれませんが、オブジェクトの要件を考慮すると、ほとんどすべてのオブジェクトで!=演算子を定義できますが、厳密な弱い順序を定義することが常に可能とは限りません。この意味で!=は、型に対する要件が少なくなるので、より良いでしょう。ただし、私はまだ残り<ます。
idclev 463035818

0

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

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

それは動作しますが、使用している場合、境界データの取り扱いが失われているので、それが誤動作を起こしやすい!=インクリメンタル問題のために(あなたは最初から知っていることを意味ならば、それ増減)の代わりに、なぜだ、使用されています。!=<>>==>


2
ロジックは完璧です。ループが終了するのiはいつ5ですか。別のことを言うつもりでしたか?
Dan Getz 2015

1
熱のためにデータが正しく転送されない場合、電磁的影響がコードを永久に実行します。ECC RAMを備えた通常のワークステーションまたはサーバーでこれを行う場合、問題はありません(論理的でも技術的でも物理的でもありません)
Markus
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.