我几乎从未见过这样的for
循环:
for (int i = 0; 5 != i; ++i)
{}
在>
循环中递增1时,是否有技术原因要使用<
或!=
而不是for
?或者这更像是一个惯例?
答案 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)
没有技术原因。但是可以降低风险,可维护性和更好地理解代码。
<
或>
是比!=
更强的限制,并且在大多数情况下都达到了完全相同的目的(我甚至会在所有实际案例中都这样说)。
答案 2 :(得分:84)
是的,有理由。如果你像这样写一个(普通的旧索引)for循环
for (int i = a; i < b; ++i){}
然后它可以按预期使用a
和b
的任何值(即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系统,则可能需要检查生成的指令及其成本。但是,如果您使用的几乎是任何传统的现代架构,则lt
,eq
,ne
和gt
之间的处理器级别差异不大。
如果您正在使用边缘案例,您会发现!=
需要三次操作(cmp
,not
,beq
)vs 2 (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;
}
一个不那么做作的例子就是你使用返回值来执行增量,接受来自用户的数据:
#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
实现者必须提供两个比较功能。所以<
已成为受欢迎的运营商。
答案 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 != i
和5 > i
是奇怪的方式来放置它,除非你在一个环境中工作,在这个环境中交换更正常的i != 5
和i < 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)不是一个正确的实现。
它会起作用,但是由于边界数据处理在使用!=
进行增量问题时丢失(这意味着你从一开始就知道它是递增还是递减),因此它很容易出现错误行为,这就是为什么而不是!=
使用了<>>==>
。