在'for'循环中增加1时,有没有技术上的理由使用>(<)而不是!=?
我几乎从来没有看到像这样的循环:
for (int i = 0; 5 != i; ++i) {}
当在for
循环中增加1时,有没有技术上的理由使用>
或<
而不是!=
? 或者这更多的是一个约定?
while (time != 6:30pm) { Work(); }
现在是晚上6点31分…该死的,现在我明天回家的下一个机会! 🙂
这表明更强的限制可以降低风险,可能更直观。
没有技术上的原因。 但是风险的减轻,可维护性和对代码的更好的理解。
在大多数情况下(甚至在所有的实际情况下,我都会这么说), <
或者>
是比<
!=
更强的限制,并且履行了同样的目的。
这里有重复的问题; 和一个有趣的答案 。
已经有答案了,但还有另外一个情况,差异很重要。 如果你有这样一个循环:
for (int i = a; i < b; i++){}
这个循环将按照预期为a
和b
任何值工作,而如果你使用了!=
在运行循环之前你必须检查a <= b
。
所以再次归结为:在大多数情况下,没有什么区别,但在某些情况下,小的差别很重要。 如果你想让所有的循环看起来相同(而不是每个循环都要考虑哪个版本更好),那么就使用<
而不是!=
。
在一些特殊的情况下,你可能需要!=
而不是<
,但这些都是非常特殊的情况,所以它们看起来不同于通常的循环。
当我第一次回答这个问题的时候,我只考虑了一个普通的老循环和int
计数器。 一旦迭代器进入游戏,情况就不同了。 循环迭代器通常如下所示:
for (Iterator it = v.begin(); it != v.end(); ++it);
原因是*它实现一个!=
运算符是一个非常弱的约束。 几乎任何两个对象都可以testing不等式,但是对于所有types的操作符都是不合理的。 特别是当容器元素没有被sorting时,迭代器不能实现它。 因此,编写这种循环的更一般的方式是使用!=
。
不过,我上面的观点仍然有效:可能有一些int
计数器,你必须使用!=
。 他们是罕见的,当他们发生,你希望他们很容易识别。 因此,用<
编写大多数int
计数器循环是很常见的。
* =没有保修。 如果我错了,请有人纠正我)
你可以有类似的东西
for(int i = 0; i<5; ++i){ ... if(...) i++; ... }
如果你的循环variables是由内部代码写的,那么i!=5
可能不会打破这个循环。 这是更安全的检查不平等。
编辑关于可读性。 不平等forms是更经常使用的方式。 因此,这是非常快的阅读,因为没有什么特别的理解(脑负荷减less,因为任务是常见的)。 所以读者利用这些习惯是很酷的。
最后但并非最不重要的,这就是所谓的防御性编程 ,意味着总是采取最强硬的情况,以避免当前和未来的错误影响该计划。
唯一不需要防御性编程的情况是,状态已经被前置条件和后置条件certificate了(但是后来certificate这是所有编程中最具防御性的)。
我会争辩一个expression式
for ( int i = 0 ; i < 100 ; ++i ) { ... }
比意思更具expression意图
for ( int i = 0 ; i != 100 ; ++i ) { ... }
前者明确地呼吁,条件是一个范围上的独家上限的testing; 后者是退出条件的二元testing。 如果循环的主体不重要,那么可能不会明显的是索引只是在for
语句本身中被修改。
当你经常使用!=
符号时,迭代器是一个重要的例子:
for(auto it = vector.begin(); it != vector.end(); ++it) { // do stuff }
当然:在实践中,我会依靠一个range-for
写同样range-for
:
for(auto & item : vector) { // do stuff }
但重点仍然是:通常比较使用==
或!=
迭代器。
循环条件是一个强制循环不variables。
假设你不看循环的主体:
for (int i = 0; i != 5; ++i) { // ? }
在这种情况下,你知道在循环迭代的开始, i
不等于5
。
for (int i = 0; i < 5; ++i) { // ? }
在这种情况下,你知道在循环迭代的开始, i
小于5
。
第二个是比第一个更多的信息,不是? 现在,程序员的意图(几乎可以肯定)是一样的,但是如果你正在寻找bug,那么从阅读一行代码中获得信心是件好事。 而第二个强制执行不变的,这意味着在第一种情况下,会有一些错误不会发生(或不会导致内存损坏)。
你知道更多关于程序的状态,从less阅读代码,而不是使用!=
。 而在现代的CPU上,他们花费的时间相同。
如果你的i
没有在循环体中被操纵, 并且它总是增加1, 并且它开始less于5
,那么就没有区别。 但是为了知道它是否被操纵,你必须确认每个事实。
其中一些事实相对容易,但是你可能会犯错。 然而,检查整个身体的循环是一个痛苦。
在C ++中,你可以编写一个indexes
types,例如:
for( const int i : indexes(0, 5) ) { // ? }
与上面两个循环中的任何一个做同样的事情,甚至到编译器优化到相同的代码。 但是,在这里,你知道 i
不能在循环体中操作,因为它被声明为const
,没有代码破坏内存。
您可以从一行代码中获得更多的信息,而不必了解上下文,就能更轻松地找出发生了什么问题。 <
在整数循环的情况下给你更多关于该行代码状态的信息,而不是!=
。
可能发生的情况是,variablesi被设置为一个很大的值,如果你只是使用!=运算符,你将会以无限循环结束。
正如Ian Newson所说的那样,你不能可靠地遍历浮动variables,并用!=
退出。 例如,
for (double x=0; x!=1; x+=0.1) {}
实际上会永远循环,因为0.1不能完全用浮点数来表示,所以计数器就会错过1.与<
终止。
(不过要注意的是,不论你是否得到0.9999 …作为最后接受的数字 – 哪一种违反了小于假设 – 或已经以1.0000000000000001退出,这基本上是未定义的行为 。
正如你从其他许多答案中可以看到的,有理由使用<而不是!=这将有助于在边缘情况下,初始条件,意外的循环计数器修改等…
老实说,我不认为你可以强调足够的传统的重要性。 对于这个例子,其他程序员很容易看到你正在尝试做什么,但这会引起重复。 编程中的工作之一就是尽可能让每个人都可读和熟悉,所以不可避免地当有人需要更新/更改你的代码时,不需要花太多的精力来弄清楚你在不同的代码块中正在做什么。 如果我看到有人使用!=
,我会假设他们使用它的原因是<
而且如果是一个很大的循环,我会仔细研究整个事情,试图弄清楚你做了什么才能做到这一点。这是浪费时间。
有几种方法可以编写任何types的代码(通常情况下),在这种情况下恰好有两种方法(三种如果计数<=和> =)。
在这种情况下,人们更喜欢>和<以确保即使在循环中发生意外事件(如错误),也不会无限循环(BAD)。 例如,考虑下面的代码。
for (int i = 1; i != 3; i++) { //More Code i = 5; //OOPS! MISTAKE! //More Code }
如果我们使用(i <3),我们将会从一个无限循环中安全,因为它放置了一个更大的限制。
它的确是你的select,无论你想在程序中的错误,closures整个事情或保持与那里的错误function。
希望这有助于!
我认为形容词“技术”是指语言行为/怪癖和编译器的副作用,例如生成的代码的性能。
为此,答案是:no(*)。 (*)是“请查阅处理器手册”。 如果您正在使用一些边缘情况下的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; }
一个不那么人为的例子就是使用返回值执行增量的地方,接受来自用户的数据:
#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(;;) } }
试试这个,input值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; } }
对于<
,也有一些约定的偏见,因为在标准容器中的sorting通常依赖于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
实现者必须提供两个比较函数。 所以<
已经成为受欢迎的运营商。
是; OpenMP不会使用!=
条件并行化循环。
使用<
最常见的原因是约定。 更多的程序员认为这样的循环是“索引在范围内”,而不是“直到索引达到最终”。 如果可以的话,坚持常规是有价值的。
另一方面,许多答案都声称使用<
form有助于避免错误。 我认为在很多情况下,这只会帮助隐藏错误。 如果循环索引应该达到最终值,而实际上它超出了它的范围,那么就有可能会导致故障(或者是另一个bug的副作用)的事情。 <
可能会延迟发现错误。 !=
更可能导致失速,死机,甚至崩溃,这将帮助您更快地发现错误。 发现错误越早,修复的成本就越低。
请注意,这个约定是数组和向量索引所特有的。 当遍历几乎任何其他types的数据结构时,您将使用迭代器(或指针)并直接检查结果值。 在这种情况下,您必须确保迭代器将达到并不超过实际的最终值。
例如,如果你是通过一个简单的Cstring,通常写更通用:
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] }
首先,如果string非常长,第二种forms会比较慢,因为strlen
是另外一个string。
使用C ++ std :: string,即使长度已经可用,也可以使用基于范围的for循环,标准algorithm或迭代器。 如果你正在使用迭代器,那么惯例是使用!=
而不是<
,如:
for (auto it = foo.begin(); it != foo.end(); ++it) { ... }
同样,迭代一棵树或一个列表或一个deque通常包括监视一个空指针或其他标记,而不是检查索引是否在一个范围内。
不使用这个构造的一个原因是浮点数。 !=
是一个非常危险的比较使用浮点数,因为即使数字看起来相同,它也很less计算为真。 <
或>
消除了这种风险。
遵循这种做法有两个相关的原因,这两个原因都与编程语言毕竟是人类将阅读的语言(等等)有关。
(1)有点冗余。 在自然语言中,我们通常提供比绝对必要的更多的信息,就像错误纠正码。 这里额外的信息就是循环variablesi
(看看我是如何在这里使用冗余的?如果你不知道'循环variables'是什么意思,或者如果你忘记了variables的名字,在阅读“循环variablesi
”后完整的信息)在循环过程中小于5,而不仅仅是与5不同。冗余增强了可读性。
(2)公约。 语言有特定的expression某些情况的标准方式。 如果你不按照既定的方式说话,你仍然会被理解,但是接收你的信息的努力是更大的,因为某些优化是行不通的。 例:
不要谈论热混搭。 只要照亮困难!
第一句话是德语成语的直译。 第二种是用同义词代替主要词汇的常见英语成语。 结果是可理解的,但需要比理解更长的时间:
别殴打灌木丛。 只要解释一下问题!
即使在第一个版本中使用的同义词比英语成语中的常规词语更适合情况,情况也是如此。 当程序员阅读代码时,类似的力量是有效的。 这也是为什么5 != i
和5 > i
是把它放在奇怪的方式, 除非你是在一个标准的环境中工作,以这种方式交换更正常的i != 5
和i < 5
。 这样的方言社区确实存在,可能是因为一致性使得它更容易记住写5 == i
而不是自然的,但容易出错i == 5
。
在这种情况下使用关系比较比其他任何事情更受欢迎的习惯。 在迭代类别及其可比性这样的概念考虑不被认为是高优先级的时代,它得到了普及。
我想说,应该尽可能地使用相等比较而不是关系比较,因为相等比较对被比较的值的要求较低。 “ 平等”是一个比“小”可比的要求。
另一个certificate在这种情况下比较平等的更广泛适用性的例子是实现unsigned
迭代降到0
的stream行难题。 它可以做到
for (unsigned i = 42; i != -1; --i) ...
请注意,以上同样适用于有符号和无符号迭代,而关系版本则用无符号types分解。
除了这些例子,循环variables将会(无意)在body内部发生变化的时候,还有其他一些使用小于或大于运算符的规则:
- 否定使代码更难理解
-
<
或>
只有一个字符,但是!=
两个
除了各种提到可以降低风险的人之外,还减less了与各种标准库组件交互所需的function重载次数。 举个例子,如果你希望你的types可以存储在一个std::set
,或者用作std::map
的键,或者用于一些search和sortingalgorithm,那么标准库通常使用std::less
比较对象,因为大多数algorithm只需要严格的弱sorting。 因此,使用<
比较而不是!=
比较(当然是有意义的),这是一个好习惯。
从语法的angular度来看,没有任何问题,但expression式5!=i
背后的逻辑并不健全。
在我看来,使用!=
来设置for循环的边界在逻辑上是不合理的,因为for循环会增加或减less迭代索引,所以设置循环迭代直到迭代索引超出边界( !=
to something )不是一个适当的实现。
它会起作用,但是它很容易出现错误行为,因为在使用!=
时,边界数据处理会丢失(这意味着从一开始就知道是递增还是递减),这就是为什么而不是!=
<>>==>
被使用。