是否有技术原因使用>(<)代替!=在'for'循环中递增1?

Zin*_*gam 196 c c++ for-loop

我几乎从未见过for像这样的循环:

for (int i = 0; 5 != i; ++i)
{}
Run Code Online (Sandbox Code Playgroud)

是否有技术原因使用><代替!=for循环中递增1 ?或者这更像是一个惯例?

Ant*_*nio 302

while (time != 6:30pm) {
    Work();
}
Run Code Online (Sandbox Code Playgroud)

现在是下午6:31 ...该死的,现在我明天回家的机会了!:)

这表明更强的限制可以降低风险,并且可能更直观易懂.

  • @AdrianMcCarthy:不一定; 它可能只是引入第二个bug,进行诊断_harder_. (27认同)
  • 有时,试图"降低风险"最终会隐藏错误.如果循环的设计应该阻止下午6:30被忽视,那么使用`<`将隐藏错误,但使用`!=`会明显表明存在问题. (24认同)
  • Iterators是我认为的@AdrianMcCarthy点的完美例子.建议的比较是!=而不是<. (5认同)
  • 我已经看到了这个错误(并且它导致了3个月的调试) - 但是这个答案完全忽略了这一点.在给定的例子中,错过最终条件是不可能的**.你甚至可以说,有意识地选择!= over <是对这一事实的强烈主张.减轻绝对不存在的风险对我来说是一种代码味道.(话虽如此,你可以同样认为<是惯用的,这是使用它的好理由.) (5认同)
  • @AdrianMcCarthy:这是我的观点; 你可能有一些你尚未见过的实例;) (4认同)
  • @PaulOgilvie 我认为 Ian Goldby 提到了问题中给出的例子,而不是我的答案。 (2认同)

Ely*_*Ely 94

没有技术原因.但是可以降低风险,可维护性并更好地理解代码.

<或者>是更强的限制,!=并且在大多数情况下达到完全相同的目的(我甚至在所有实际情况中都说).

有重复的问题在这里 ; 还有一个有趣的答案.

  • *"但是可以降低风险,可维护性和更好地理解代码."*所有这些都是正确的技术原因.:-) (7认同)
  • 有些类型(如迭代器)不支持<或>,但支持==和!=. (6认同)

for*_*818 84

是的,有一个原因.如果你像这样写一个(普通的旧索引)循环

for (int i = a; i < b; ++i){}
Run Code Online (Sandbox Code Playgroud)

那么它对于任何a和的值都可以正常工作b(a > b如果你曾经使用过,则代替无限时的零迭代i == b;).

另一方面,对于你要编写的迭代器

for (auto it = begin; it != end; ++it) 
Run Code Online (Sandbox Code Playgroud)

因为任何迭代器都应该实现一个operator!=,但不是每个迭代器都可以提供一个operator<.

也是基于范围的循环

for (auto e : v)
Run Code Online (Sandbox Code Playgroud)

不只是花哨的糖,但它们可以显着减少编写错误代码的机会.

  • 除了`!=`和迭代器之外的任何东西都是非常危险的,因为*有时*迭代器可以小于比较(例如它是一个指针)但是比较没有意义(它是一个链表). (9认同)
  • 很好的例子.我对`!=`而不是`<`的情况感兴趣.我个人从未使用过我的想法. (4认同)
  • 认真学习使用空白。 (2认同)

joh*_*n d 69

你可以有类似的东西

for(int i = 0; i<5; ++i){
    ...
    if(...) i++;
    ...
}
Run Code Online (Sandbox Code Playgroud)

如果您的循环变量由内部代码写入,则i!=5可能不会破坏该循环.检查不平等是更安全的.

编辑可读性.不平等形式更常用.因此,这是非常快速的阅读,因为没有什么特别需要理解(大脑负荷减少,因为任务很常见).所以读者很容易利用这些习惯.

  • 这种内在的"if(condition)then i ++"是我想到的第一件事. (6认同)
  • 期待这是最高投票答案; 我很惊讶我必须滚动这么远才能找到它.这对说服新手程序员比"好吧,你应该使用最强的条件"更有帮助.其他答案,如果它们保持最佳状态,应该将其作为一个明确而明显的解释,说明OP提出的代码可能出现的问题. (3认同)
  • @msouth我喜欢它,当意外的生产行为暴露我的错误,不是吗?:-) (3认同)
  • @msouth看,我们所要做的就是永远不要犯任何错误,一切都会好起来的.Simples. (3认同)

Pau*_*vie 48

最后但并非最不重要的是,这被称为防御性编程,意味着始终采取最强的案例来避免影响该计划的当前和未来错误.

唯一不需要防御性编程的情况是状态已经通过前后条件得到证实(但后来证明这是所有编程中最具防御性的).

  • 支持这一点的一个很好的例子:内存容易受到辐射引起的位翻转([SEU](https://en.wikipedia.org/wiki/Single_event_upset)).当`int`用于`i!= 15`条件时,如果翻转第四位以上的任何内容,计数器将运行很长时间,特别是在`sizeof(int)`为64的机器上.SEU是一个在高海拔或太空中(由于辐射较高),或在大型超级计算机中(因为存在如此多的存储器)非常真实的关注. (2认同)
  • 认为当辐射改变你的记忆时你的程序会运行正常是一种乐观的方式和反灾难的思维方式......好评!:) (2认同)

Nic*_*rey 37

我会说像一个表达式

for ( int i = 0 ; i < 100 ; ++i )
{
  ...
}
Run Code Online (Sandbox Code Playgroud)

意图表达更具表现力

for ( int i = 0 ; i != 100 ; ++i )
{
  ...
}
Run Code Online (Sandbox Code Playgroud)

前者清楚地指出,条件是对范围的排他上限的测试; 后者是退出条件的二元测试.如果循环体是非平凡的,那么索引只能在for语句本身中修改可能并不明显.

  • "我希望这个循环运行最多5次"对"我想根据需要多次运行这个循环,除了5次,我不想要." (13认同)

Esc*_*alo 21

当您最常使用!=符号时,迭代器是一个重要的情况:

for(auto it = vector.begin(); it != vector.end(); ++it) {
 // do stuff
}
Run Code Online (Sandbox Code Playgroud)

当然:在实践中我会依赖于range-for:

for(auto & item : vector) {
 // do stuff
}
Run Code Online (Sandbox Code Playgroud)

但重点仍然是:通常使用==或比较迭代器!=.

  • 迭代器通常只有相等/不等的概念,而没有排序,这就是为什么你对它们使用 `!=` 的原因。例如,在 `std::vector` 中你_可以_使用 `&lt;` 作为迭代器绑定到索引/指针,但在 `std::set` 中没有如此严格的排序,因为它遍历二叉树. (2认同)

Yak*_*ont 18

循环条件是强制循环不变量.

假设您没有查看循环体:

for (int i = 0; i != 5; ++i)
{
  // ?
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您知道在循环迭代开始时i不相等5.

for (int i = 0; i < 5; ++i)
{
  // ?
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您知道在循环迭代开始时i小于5.

第二个是比第一个更多,更多的信息,不是吗?现在,程序员的意图(几乎可以肯定)是相同的,但是如果你正在寻找bug,那么通过阅读一行代码就有信心是一件好事.而第二种强制执行不变量,这意味着在第二种情况下,在第一种情况下会咬你的一些错误就不会发生(或者说不会导致内存损坏).

你知道更多关于该程序的状态,从阅读更少的代码,用<!=.在现代CPU上,它们花费的时间相同,没有差别.

如果你i没有在循环体中被操纵,并且它总是增加1,并且它开始小于5,则没有区别.但是为了知道它是否被操纵,你必须确认这些事实.

其中一些事实相对容易,但你可能会出错.然而,检查整个循环体是一种痛苦.

在C++中,您可以编写一个indexes类型:

for( const int i : indexes(0, 5) )
{
  // ?
}
Run Code Online (Sandbox Code Playgroud)

与上述两个for循环中的任何一个做同样的事情,甚至到编译器优化它到相同的代码.但在这里,你知道i不能在循环体被操纵,因为它被宣布const,而代码破坏内存.

您可以从一行代码中获得的信息越多,无需了解上下文,就越容易找到出错的地方. <在整数循环的情况下,为您提供有关该行代码状态的更多信息!=.


Mos*_*teM 13

可能会发生变量i设置为某个较大的值,如果您只是使用!=运算符,您将最终进入无限循环.


Rya*_*anP 12

从其他众多答案中可以看出,有理由使用<而不是!=这将有助于边缘情况,初始条件,无意的循环计数器修改等...

老实说,我认为你不能强调公约的重要性.对于这个例子,其他程序员很容易看到你想要做什么,但它会导致双重考虑.编程中的一项工作是让每个人都尽可能地阅读和熟悉,所以当有人不得不更新/更改你的代码时,不需要花费很多精力来弄清楚你在不同的代码块中做了什么. .如果我看到有人使用!=,我会假设他们使用它而不是它的原因<,如果它是一个大循环我会仔细研究整个事情,试图找出你所做的那些必要的......那就是浪费时间.


Meh*_*dad 12

是; OpenMP不会将循环与!=条件并行化.


lef*_*out 12

正如Ian Newson已经说过的那样,你无法可靠地循环浮点变量并退出!=.例如,

for (double x=0; x!=1; x+=0.1) {}
Run Code Online (Sandbox Code Playgroud)

实际上将永远循环,因为0.1不能完全以浮点表示,因此计数器差距错过1.随着<它终止.

(但请注意,基本上未定义的行为是否得到0.9999 ...作为最后接受的数字 - 哪种违反小于假设 - 或已经退出1.0000000000000001.)


kfs*_*one 10

我把形容词"技术"用来表示语言行为/怪癖和编译器副作用,例如生成代码的性能.

为此,答案是:不(*).(*)是"请参阅处理器手册".如果您正在使用某些边缘情况RISC或FPGA系统,则可能需要检查生成的指令及其成本.但是,如果你正在使用几乎任何传统的现代建筑,那么在成本之间没有显著处理器水平差lt,eq,negt.

如果您使用的是边缘的情况下,你可能会发现,!=需要三个操作(cmp,not,beq)与两(cmp,blt 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;
}
Run Code Online (Sandbox Code Playgroud)

一个不太习惯的例子是你使用返回值来执行增量,接受来自用户的数据:

#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(;;)
    }
}
Run Code Online (Sandbox Code Playgroud)

试试这个并输入值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;
    }
}
Run Code Online (Sandbox Code Playgroud)

但你可能想要的是

#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;
    }
}
Run Code Online (Sandbox Code Playgroud)

还有一些惯例偏向<,因为标准容器中的排序经常依赖operator<,例如在几个STL容器中的散列通过说法来确定平等

if (lhs < rhs) // T.operator <
    lessthan
else if (rhs < lhs) // T.operator < again
    greaterthan
else
    equal
Run Code Online (Sandbox Code Playgroud)

如果lhs并且rhs是用户定义的类,则将此代码编写为

if (lhs < rhs) // requires T.operator<
    lessthan
else if (lhs > rhs) // requires T.operator>
    greaterthan
else
    equal
Run Code Online (Sandbox Code Playgroud)

实现者必须提供两个比较功能.因此<成为了受欢迎的运营商.


小智 9

有几种方法可以编写任何类型的代码(通常),在这种情况下恰好有两种方法(如果算上<=和> =则为三种).

在这种情况下,人们更喜欢>和<以确保即使在循环中发生意外事件(如错误),它也不会无限循环(BAD).例如,请考虑以下代码.

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

如果我们使用(i <3),我们将无限循环,因为它设置了更大的限制.

无论你是希望程序中的错误关闭整个程序还是继续使用那里的bug,你真的是你的选择.

希望这有帮助!


Adr*_*thy 7

最常见的使用原因<是惯例.更多的程序员认为像这样的循环是"当索引在范围内"而不是"直到索引到达终点".如果可以,有价值的是坚持惯例.

另一方面,这里的许多答案声称使用<表单有助于避免错误.我认为在很多情况下这只会有助于隐藏错误.如果循环索引应该达到最终值,而实际上它超出了它,则会发生一些您没想到的可能导致故障的事情(或者是另一个错误的副作用).这<可能会延迟发现这个bug.该!=更可能导致失速,挂起,甚至崩溃,这将有助于你发现的bug越早.发现错误越快,修复的成本就越低.

请注意,此约定是数组和向量索引所特有的.遍历几乎任何其他类型的数据结构时,您将使用迭代器(或指针)并直接检查结束值.在这些情况下,您必须确保迭代器将达到并且不会超过实际的最终值.

例如,如果您单步执行普通的C字符串,通常更常见的是:

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

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

首先,如果字符串很长,第二种形式会更慢,因为strlen另一种形式是字符串.

使用C++ std :: string,您可以使用基于范围的for循环,标准算法或迭代器,即使长度很容易获得.如果您正在使用迭代器,那么约定是使用!=而不是<,如下所示:

for (auto it = foo.begin(); it != foo.end(); ++it) { ... }
Run Code Online (Sandbox Code Playgroud)

类似地,迭代树或列表或双端队列通常涉及观察空指针或其他标记,而不是检查索引是否保持在范围内.

  • 你非常正确地强调习语在编程中的重要性.如果我看一些代码并且它有一个熟悉的模式,我不需要浪费时间推断模式,但可以直接进入它的核心.我想这是我对Yoda比较的主要把握 - 他们看起来并不惯用,所以我最终必须阅读两次以确保它们意味着我认为它们的意思. (3认同)

小智 7

遵循这种做法有两个相关的原因,这两个原因都与编程语言毕竟是一种人类会阅读的语言(其中包括)有关.

(1)有点冗余.在自然语言中,我们通常提供的信息比严格必要的信息更多,就像纠错码一样.这里额外的信息是循环变量i(看看我如何在这里使用冗余?如果你不知道'循环变量'是什么意思,或者你忘记了变量的名称,在阅读"循环变量i"后你就完整了信息)在循环期间小于5,而不仅仅是5.冗余增强了可读性.

(2)公约.语言具有表达某些情况的特定标准方式.如果你不遵循既定的说法,你仍然会被理解,但是你的信息收件人的努力更大,因为某些优化是行不通的.例:

不要在热的土豆泥周围说话.只是阐明难度!

第一句是德语成语的字面翻译.第二种是常见的英语习语,主要词语被同义词取代.结果是可理解的,但需要花费更长的时间才能理解:

不要在灌木丛中殴打.只是解释一下问题!

即使在第一版中使用的同义词碰巧比英语习语中的常规词更适合情况的情况下也是如此.当程序员阅读代码时,类似的力量也会生效.这也是为什么5 != i并且5 > i是奇怪的方式,除非你在一个标准的环境中工作,以更换正常i != 5i < 5这种方式.这样的方言社区确实存在,可能是因为一致性使得更容易记住编写5 == i而不是自然但容易出错i == 5.


Ian*_*son 7

不使用此构造的一个原因是浮点数.!=使用浮点数是一个非常危险的比较,因为即使数字看起来相同,它也很少评估为真.<>消除这种风险.


AnT*_*AnT 6

在这种情况下使用关系比较更像是一种流行的习惯而不是其他任何东西.当迭代器类别及其可比性等概念性考虑因素不被视为高度优先时,它在时代中获得了普及.

我会说,应该尽可能使用相等比较而不是关系比较,因为相等比较对所比较的值提出的要求较少.正在EqualityComparable的比做一个较小的需求小于关系.

另一个证明平等比较在这种情况下更广泛适用性的例子是实现unsigned迭代的流行难题0.它可以作为

for (unsigned i = 42; i != -1; --i)
  ...
Run Code Online (Sandbox Code Playgroud)

请注意,上述内容同样适用于有符号和无符号迭代,而关系版本则使用无符号类型进行分解.