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


198

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

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

在循环中加1时是否有使用><代替的技术原因?还是这更像是惯例?!=for


104
它使它更具可读性,此外,如果您更改i++i+=2(例如),它将运行很长时间(或可能永远)。现在,由于通常<用于将迭代器增加1 以上<的情况,因此也可以用于将迭代器增加1 的情况(出于一致性考虑)。
barak manos

112
5 != i是邪恶的
Slava

27
您应该始终意识到自己在做什么,但是无论如何都会出错。使用<减少了有人在此代码中引入错误的可能性。
Aurast 2015年

40
@OleTange如果要使用浮点数进行迭代,则已经搞砸了
。– CaptainCodeman

29
@Slava ...不是邪恶的。你显然并没有通过简单的一直在C位错字使得之间的差异if ( i == 5 ) ...if ( i = 5 )。完全不同的东西。反转操作数可以避免此问题:由于文字不是lvals,因此无法将它们赋值,并且编译器将输入错字。@Zingam:好的防御性编程!
尼古拉斯·凯里2015年

Answers:


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

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

这表明较强的限制可以减轻风险,并且可能更直观地理解。


25
有时,尝试“缓解风险”最终只会隐藏该错误。如果循环的设计应避免6:30 pm突然被忽视,则使用<将隐藏该错误,但使用!=则会明显表明存在问题。
阿德里安·麦卡锡

27
@AdrianMcCarthy:不一定;它可能会引入第二个错误,使诊断更加困难
Lightness Races in Orbit

4
@AdrianMcCarthy:这是我的观点;您可能会有一些您尚未见过的实例;)
轨道

6
我认为,迭代器是@AdrianMcCarthy点的完美示例。建议对它们的比较是!=而不是<。
塔肯2015年

5
我已经确切地看到了这个错误(它导致了3个月的调试)-但是这个答案完全没有意义。在给定的示例中,不可能错过最终条件。您甚至可以说,有意识地选择!=而不是<是对这一事实的有力断言。降低绝对不存在的风险对我来说是一种代码味道。(话虽如此,您也可以辩称<是惯用的,这就是使用它的充分理由。)
Ian Goldby

95

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

<>更强的限制!=大多数情况下并能达到完全相同的目的(我什至要在所有实际情况下说)。

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


6
有些类型(例如迭代器)不支持<或>,但支持==和!=。
西蒙B

1
这是一个与int的for循环。没有迭代器。
2015年

7
“但是可以降低风险,可维护性和对代码的更好理解。” 所有这些都是技术上的原因。:-)
TJ Crowder

@TJCrowder当您只想谈论计算机将要执行的操作时,这是什么类型的原因?
Brilliand

1
@DanSheppard我通常认为减轻风险和可维护性是业务原因,而更好地理解代码则是社会原因(尽管在这种情况下,所有这些原因都已应用于技术问题)。“降低风险”的考虑绝不限于技术问题,如果您发现自己与另一群人一起工作(即使所涉及的机器完全没有变化),则“更好地理解代码”的考虑也会发生变化。 )。
Brilliand

85

是的,这是有原因的。如果您这样编写(基于普通旧索引的)for循环

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

那么它对于任何 ab(即零迭代时a > b,如果你使用过的,而不是无限i == b;)。

另一方面,对于迭代器,您需要编写

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

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

也是基于范围的for循环

for (auto e : v)

不仅是花哨的糖,而且可测量地减少了编写错误代码的机会。


4
很好的例子。我对!=代替的案例感兴趣<。我个人从未使用过我的想法。
2015年

@Elyasin我找不到一个好容器,也不想发布一个坏容器:想象一下,您有某种圆形容器,其中您以+ x模量增加了容器N的模数,并且想在您停止循环时达到一定的指标...。那真是一个非常糟糕的例子。也许我会找到一个更好的产品
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
这种类型的内部“如果(条件)则为i ++”是我想到的第一件事。
WernerCD

3
期望这是投票率最高的答案;我很惊讶我不得不滚动到很远才能找到它。这对于说服新手程序员比“好吧,您应该使用最强条件”要有效得多。如果其他答案排在最前面,则应将其结合起来,作为对OP提议的代码可能会出问题的清晰清晰的解释。
msouth,2015年

1
@msouth我完全同意,但是我的POV是相当主观的;)
johan d

3
@msouth我喜欢它,因为意外的生产行为暴露了我的错误,不是吗?:-)
deworde

3
@msouth看,我们要做的就是永远不会犯任何错误,一切都会好起来的。简单。
deworde

48

最后但并非最不重要的是,这称为防御性编程,这意味着始终采用最强的情况,以避免当前和将来的错误影响程序。

唯一不需要防御性编程的情况是状态已通过前置条件和后置条件证明(但随后证明这是所有编程中最具防御性的)。


2
支持此的一个很好的例子:内存容易受到辐射引起的位翻转(SEU)的影响。当将a int作为i != 15条件使用时,如果第四位以上的内容被翻转,则计数器将运行很长时间,尤其是在sizeof(int)64 位的机器上。SEU在高海拔或太空中(由于较高的辐射)是一个非常现实的问题。 ,或在大型超级计算机中(因为存在大量内存)。
乔什·桑福德

2
认为当辐射改变您的内存时您的程序可以正常运行,这是一种乐观的方法,也是一种灾难性的思维方式。:)
路易斯·路易斯科罗拉多州

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
迭代器通常只具有相等/不相等的概念,而没有顺序,这就是为什么要使用!=它们。例如,在a中,std::vector可以使用<迭代器绑定到索引/指针,但是在a中std::set,没有严格的排序,因为它遍历了二叉树。
Martin C.

19

循环条件是强制循环不变式。

假设您没有看循环的主体:

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

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

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

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

第二个比第一个多得多的信息,不是吗?现在,程序员的意图(几乎可以肯定)是相同的,但是如果您正在寻找错误,那么从阅读一行代码中获得自信是一件好事。第二个强制执行该不变性,这意味着在第二种情况下不会发生(或者不会导致内存损坏)的某些错误在第一种情况下会叮咬您。

<相比,通过阅读更少的代码,您可以了解更多有关程序状态的信息!=。在现代CPU上,它们花费的时间相同,没有差异。

如果您i没有在循环体操作,并且它总是增加1,并且比开始少5,则不会有任何区别。但是,要知道它是否被操纵,您必须确认所有这些事实。

其中一些事实相对容易,但您可能会出错。但是,检查循环的整个过程很痛苦。

在C ++中,您可以编写如下indexes类型:

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

与上述两个for循环中的任何一个执行相同的操作,甚至由编译器将其优化为相同的代码。但在这里,你知道i不能在循环体被操纵,因为它被宣布const,而代码破坏内存。

在无需理解上下文的情况下,可以从代码行中获得的信息越多,越容易找出问题所在。 <在整数循环的情况下,与该行相比,!=它为您提供了关于该行代码状态的更多信息。



13

可能会将变量i设置为较大的值,如果仅使用!=运算符,则将陷入无限循环。


1
并非没有尽头-在C的大多数实现中,i当C 达到最大值时,它们会默默地回绕。但是,是的,很可能比您准备等待的时间更长。
usr2564301 2015年

12

从其他众多答案中可以看出,有理由使用<代替!=,这在边缘情况,初始条件,意外循环计数器修改等方面会有所帮助。

老实说,我认为您不能足够强调约定的重要性。对于此示例,其他程序员可以很容易地看到您要尝试执行的操作,但这会引起重复。编程时的一项工作是使每个人都尽可能容易阅读和熟悉,因此不可避免地,当有人不得不更新/更改您的代码时,无需花费很多精力就能弄清楚您在不同代码块中的工作。如果我看到有人使用它!=,我会认为是他们使用它而不是使用它的原因<,如果那是一个很大的循环,我会仔细检查整个过程,以弄清楚您做了什么使得那是必要的……这就是浪费时间。


12

正如Ian Newson所说,您无法可靠地循环访问浮动变量并使用退出!=。例如,

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

实际上将永远循环,因为0.1不能精确地表示为浮点数,因此计数器将丢失1 <

(但是请注意,无论您是否获得0.9999 ...作为最后一个接受的数字(这种违反类型小于小于的假设)还是已经以1.0000000000000001退出,这基本上是未定义的行为。)


10

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

为此,答案是:no(*)。(*)是“请查阅您的处理器手册”。如果使用的是边缘情况下的RISC或FPGA系统,则可能需要检查生成了哪些指令以及它们的成本。但是,如果你正在使用几乎任何传统的现代建筑,那么在成本没有显著处理器级差之间lteqnegt

如果您使用的是边缘的情况下,你可能会发现,!=需要三个操作(cmpnotbeq)与两(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

如果lhsrhs是用户定义的类,则将该代码编写为

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

实现者必须提供两个比较功能。因此<已成为偏爱的运营商。


为了提高可读性,i + =步骤应位于for控制部分中。如果您将步进= 0
Mikkel Christiansen

1
虽然这将有助于提高良好代码的可读性,但我想强调一下错误版本中的缺陷
kfsone 2015年

9

通常有几种方法可以编写任何类型的代码,在这种情况下,碰巧只有两种方法(如果计数<=和> =则为三种)。

在这种情况下,人们倾向于使用>和<来确保即使循环中发生了意外事件(例如错误),也不会无限循环(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]
}

一方面,如果字符串很长,则第二种形式会变慢,因为这strlen是另一种通过字符串的方式。

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

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

同样,对树或列表或双端队列进行迭代通常涉及监视空指针或其他标记,而不是检查索引是否仍在范围内。


3
您很正确地强调编程中成语的重要性。如果我看一些代码并且它具有熟悉的模式,那么我不需要浪费时间来推断该模式,而是可以直接进入它的核心。我想这是Yoda比较的主要优势-它们看起来并不惯用,因此我最终不得不阅读两次以确保它们代表我的意思。
Toby Speight 2015年

7

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


如果循环迭代器是一个浮点数,则!=vs <是最少的问题。
伦丁

7

遵循这种做法有两个相关的原因,两者都与一个事实有关,即编程语言毕竟是一种人类(以及其他人)可以阅读的语言。

(1)有点冗余。用自然语言,我们通常会提供比严格必要的更多的信息,这很像一个纠错码。这里的额外信息是循环变量i(请参阅我在这里如何使用冗余?如果您不知道“循环变量”是什么意思,或者如果忘记了变量的名称,则在阅读“循环变量i”之后,您将拥有完整的信息)在循环中小于5,不仅与5不同。冗余提高了可读性。

(2)公约。语言具有表达特定情况的特定标准方法。如果您不遵循既定的说话方式,那么您仍然会被理解,但是由于某些优化无法奏效,因此邮件接收者会付出更大的努力。例:

不要谈论热糊状食物。只是说明困难!

第一句话是德语成语的字面翻译。第二个是常见的英语习语,主要单词被同义词代替。结果是可理解的,但要花更长的时间理解:

不要在灌木丛中跳动。只是说明问题!

即使第一版中使用的同义词比英语习语中的常规单词更适合这种情况,也是如此。当程序员阅读代码时,类似的作用也起作用。这也是为什么5 != i5 > i是把它的怪异方式,除非你是在它是标准的交换更加正常的环境中工作i != 5,并i < 5以这种方式。这样的方言社区确实存在,可能是因为一致性使记起来更容易记起来,5 == i而不是自然而容易出错的记号i == 5


1
奥斯特。同样,不会将测试编写为i < 17+(36/-3)(即使这些是在其他地方使用的常量;为了清楚起见,您也必须写出它们的名称!)。
usr2564301 2015年

6

在这种情况下,使用关系比较是最流行的习惯。在迭代器类别及其可比性等概念性考虑不被视为高度优先的时代,它开始流行。

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

另一个证明平等比较在这种情况下具有更广泛适用性的例子是普遍的难题,即实施unsigned迭代到0。它可以做到

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

请注意,以上内容同样适用于有符号和无符号迭代,而关系版本按无符号类型分解。


2

除了示例(循环变量将在体内发生(无意地)更改)的示例外,还有其他一些原因可以使用小于或大于运算符:

  • 否定使代码更难理解
  • <>只有一个字符,但!=两个

4
第二个子弹充其量是无聊的理由。该代码应正确且清晰。紧凑远不那么重要。
Jonathan Leffler

@JonathanLeffler好吧,在TDD REPL中,额外的字符需要多一纳秒的解析时间,并且经过十亿次迭代才能发现由编写引起的错误5 != $i,这花了我一生的时间。呃…… 窃笑
主教

1

除了提到它可以减轻风险的各种人员外,它还减少了与各种标准库组件进行交互所需的函数重载次数。举例来说,如果您希望您的类型可存储在中std::set,或用作的键std::map,或与某些搜索和排序算法一起使用,则标准库通常用于std::less比较对象,因为大多数算法只需要严格的弱排序即可。因此,使用<比较而不是!=比较(当然是有意义的)成为一种好习惯。


1
您可能对重载的数量是正确的,但是在考虑对象的要求时,几乎可以为任何对象定义一个!=运算符,但并非总是可以定义严格的弱排序。从这个意义上讲!=会更好,因为它对类型的要求更少。但是,我仍然留在<
idclev 463035818

0

从语法角度来看没有问题,但是该表达式背后的逻辑5!=i并不完善。

在我看来,使用!=for设置for循环的边界在逻辑上是不合理的,因为for循环会增加或减少迭代索引,因此将循环设置为迭代直到迭代索引变得越界(超出!=某个范围)并不是一个合理的选择。正确实施。

它的工作,但很容易出现不当行为,因为边界数据处理使用时,会丢失!=一个增量的问题(这意味着你从一开始就知道这是否增加或减少),这就是为什么,而不是!=<>>==>被使用。


2
逻辑很好。如果i5退出循环。您是说其他话吗?
丹·盖茨

1
如果由于热而导致数据无法正确传输,则电磁会永久影响代码运行。如果您在具有ECC RAM的常规工作站或服务器上执行此操作,则不会出现问题(逻辑上,技术上或物理上都没有)
Markus
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.