我几乎从未见过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 ...该死的,现在我明天回家的机会了!:)
这表明更强的限制可以降低风险,并且可能更直观易懂.
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)
不只是花哨的糖,但它们可以显着减少编写错误代码的机会.
joh*_*n d 69
你可以有类似的东西
for(int i = 0; i<5; ++i){
...
if(...) i++;
...
}
Run Code Online (Sandbox Code Playgroud)
如果您的循环变量由内部代码写入,则i!=5可能不会破坏该循环.检查不平等是更安全的.
编辑可读性.不平等形式更常用.因此,这是非常快速的阅读,因为没有什么特别需要理解(大脑负荷减少,因为任务很常见).所以读者很容易利用这些习惯.
Pau*_*vie 48
最后但并非最不重要的是,这被称为防御性编程,意味着始终采取最强的案例来避免影响该计划的当前和未来错误.
唯一不需要防御性编程的情况是状态已经通过前后条件得到证实(但后来证明这是所有编程中最具防御性的).
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语句本身中修改可能并不明显.
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)
但重点仍然是:通常使用==或比较迭代器!=.
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,而代码破坏内存.
您可以从一行代码中获得的信息越多,无需了解上下文,就越容易找到出错的地方. <在整数循环的情况下,为您提供有关该行代码状态的更多信息!=.
Rya*_*anP 12
从其他众多答案中可以看出,有理由使用<而不是!=这将有助于边缘情况,初始条件,无意的循环计数器修改等...
老实说,我认为你不能强调公约的重要性.对于这个例子,其他程序员很容易看到你想要做什么,但它会导致双重考虑.编程中的一项工作是让每个人都尽可能地阅读和熟悉,所以当有人不得不更新/更改你的代码时,不需要花费很多精力来弄清楚你在不同的代码块中做了什么. .如果我看到有人使用!=,我会假设他们使用它而不是它的原因<,如果它是一个大循环我会仔细研究整个事情,试图找出你所做的那些必要的......那就是浪费时间.
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,ne和gt.
如果您使用的是边缘的情况下,你可能会发现,!=需要三个操作(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,你真的是你的选择.
希望这有帮助!
最常见的使用原因<是惯例.更多的程序员认为像这样的循环是"当索引在范围内"而不是"直到索引到达终点".如果可以,有价值的是坚持惯例.
另一方面,这里的许多答案声称使用<表单有助于避免错误.我认为在很多情况下这只会有助于隐藏错误.如果循环索引应该达到最终值,而实际上它超出了它,则会发生一些您没想到的可能导致故障的事情(或者是另一个错误的副作用).这<可能会延迟发现这个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)
类似地,迭代树或列表或双端队列通常涉及观察空指针或其他标记,而不是检查索引是否保持在范围内.
小智 7
遵循这种做法有两个相关的原因,这两个原因都与编程语言毕竟是一种人类会阅读的语言(其中包括)有关.
(1)有点冗余.在自然语言中,我们通常提供的信息比严格必要的信息更多,就像纠错码一样.这里额外的信息是循环变量i(看看我如何在这里使用冗余?如果你不知道'循环变量'是什么意思,或者你忘记了变量的名称,在阅读"循环变量i"后你就完整了信息)在循环期间小于5,而不仅仅是5.冗余增强了可读性.
(2)公约.语言具有表达某些情况的特定标准方式.如果你不遵循既定的说法,你仍然会被理解,但是你的信息收件人的努力更大,因为某些优化是行不通的.例:
不要在热的土豆泥周围说话.只是阐明难度!
第一句是德语成语的字面翻译.第二种是常见的英语习语,主要词语被同义词取代.结果是可理解的,但需要花费更长的时间才能理解:
不要在灌木丛中殴打.只是解释一下问题!
即使在第一版中使用的同义词碰巧比英语习语中的常规词更适合情况的情况下也是如此.当程序员阅读代码时,类似的力量也会生效.这也是为什么5 != i并且5 > i是奇怪的方式,除非你在一个标准的环境中工作,以更换正常i != 5和i < 5这种方式.这样的方言社区确实存在,可能是因为一致性使得更容易记住编写5 == i而不是自然但容易出错i == 5.
在这种情况下使用关系比较更像是一种流行的习惯而不是其他任何东西.当迭代器类别及其可比性等概念性考虑因素不被视为高度优先时,它在时代中获得了普及.
我会说,应该尽可能使用相等比较而不是关系比较,因为相等比较对所比较的值提出的要求较少.正在EqualityComparable的比做一个较小的需求小于关系.
另一个证明平等比较在这种情况下更广泛适用性的例子是实现unsigned迭代的流行难题0.它可以作为
for (unsigned i = 42; i != -1; --i)
...
Run Code Online (Sandbox Code Playgroud)
请注意,上述内容同样适用于有符号和无符号迭代,而关系版本则使用无符号类型进行分解.