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

时间:2015-07-16 13:29:37

标签: c++ c for-loop

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

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

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

21 个答案:

答案 0 :(得分:300)

$duplicates = DB::table('table')
    ->select('subjectlist_id', 'article_id')
    ->whereIn('group_id', array(1,2,3))
    ->groupBy('subjectlist_id', 'article_id')
    ->havingRaw('COUNT(*) > 1')
    ->update(['marked' => 'done']);

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

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

答案 1 :(得分:94)

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

<>是比!=更强的限制,并且在大多数情况下都达到了完全相同的目的(我甚至会在所有实际案例中都这样说)。

有重复的问题here;还有一个有趣的answer

答案 2 :(得分:84)

是的,有理由。如果你像这样写一个(普通的旧索引)for循环

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

然后它可以按预期使用ab的任何值(即a > b时的零迭代,而不是使用i == b;时的无限)。

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

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

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

也是基于范围的循环

for (auto e : v)

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

答案 3 :(得分:68)

您可以拥有类似

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

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

关于可读性的

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

答案 4 :(得分:48)

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

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

答案 5 :(得分:37)

我认为像

这样的表达
for ( int i = 0 ; i < 100 ; ++i )
{
  ...
}

更多表达意图而不是

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

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

答案 6 :(得分:21)

当您最常使用for(auto it = vector.begin(); it != vector.end(); ++it) { // do stuff } 表示法时,迭代器是一个重要的案例:

range-for

当然:在实践中,我会依赖for(auto & item : vector) { // do stuff }

来写同样的东西
==

但重点仍然是:通常使用!=或{{1}}来比较迭代器。

答案 7 :(得分:18)

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

假设你没有看到循环的主体:

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,而代码没有破坏内存。

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

答案 8 :(得分:13)

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

答案 9 :(得分:12)

正如Ian Newson所说,你不能可靠地循环浮动变量并退出!=。例如,

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

实际上会永远循环,因为0.1不能完全以浮点表示,因此计数器会错过1.使用<它会终止。

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

答案 10 :(得分:12)

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

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

答案 11 :(得分:11)

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

答案 12 :(得分:10)

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

为此,答案是:不(*)。 (*)是“请参阅处理器手册”。如果您正在使用某些边缘情况RISC或FPGA系统,则可能需要检查生成的指令及其成本。但是,如果您使用的几乎是任何传统的现代架构,则lteqnegt之间的处理器级别差异不大。

如果您正在使用边缘案例,您会发现!=需要三次操作(cmpnotbeq)vs 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

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

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

实现者必须提供两个比较功能。所以<已成为受欢迎的运营商。

答案 13 :(得分:9)

有几种方法可以编写任何类型的代码(通常),在这种情况下恰好有两种方法(如果算上三种方式&lt; =和&gt; =)。

在这种情况下,人们更喜欢&gt;和&lt;为了确保即使循环中出现意外情况(如错误),它也不会无限循环(BAD)。例如,请考虑以下代码。

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

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

你真的可以选择是否要在程序中出错来关闭整个程序,或者继续使用那里的bug。

希望这有帮助!

答案 14 :(得分: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) { ... }

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

答案 15 :(得分:7)

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

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

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

  

不要谈论热的糊状物。只是阐明难度!

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

  

不要在丛林周围殴打。只是解释一下这个问题!

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

答案 16 :(得分:7)

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

答案 17 :(得分:6)

在这种情况下使用关系比较比其他任何事情都更受欢迎。当迭代器类别及其可比性等概念性考虑因素不被视为高度优先时,它的受欢迎程度回升。

我认为应该尽可能使用相等比较而不是关系比较,因为相等比较对所比较的值提出的要求较少。 EqualityComparable LessThanComparable 要求更低。

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

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

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

答案 18 :(得分:2)

除了示例之外,循环变量将(无意)在主体内部发生变化,还有其他规则可以使用小于或大于运算符:

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

答案 19 :(得分:1)

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

答案 20 :(得分:0)

从语法角度来看没有问题,但表达式5!=i背后的逻辑不合理。

在我看来,使用!=来设置for循环的边界在逻辑上是不合理的,因为for循环会递增或递减迭代索引,因此将循环设置为迭代直到迭代索引变为out bounds(!= to something)不是一个正确的实现。

它会起作用,但是由于边界数据处理在使用!=进行增量问题时丢失(这意味着你从一开始就知道它是递增还是递减),因此它很容易出现错误行为,这就是为什么而不是!=使用了<>>==>