while - java遍历




在'for'循环中递增1时格式化的技术原因? (20)

FORTRAN's DO loop and BASIC's FOR loop implemented < (actually <= ) for positive increments. Not sure what COBOL did, but I suspect it was similar. So this approach was "natural" to the designers and users of "new" languages like C.

Additionally, < is more likely than != to terminate in erroneous situations, and is equally valid for integer and floating point values.

The first point above is the probable reason the style got started, the second is the main reason it continues.

在整个网络上,代码示例都有for循环,如下所示:

for(int i = 0; i < 5; i++)

我使用以下格式:

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

我这样做是因为我认为它更有效率,但这在大多数情况下真的很重要吗?


I remember one code segment where the i was getting incremented by 2 instead of 1 due to some mistake and it was causing it to go in infinite loop. So it is better to have this loop as shown in the first option. This is more readable also. Because i != 5 and i < 5 conveys two different meaning to the reader. Also if you are increasing the loop variable then i<5 is suppose to end some point of time while i != 5 may never end because of some mistake.


I think in the end it boils down to personal preference.
I like the idea of

for(int i = 0; i < 5; i++)

过度

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

due to there being a chance of the value of i jumping past 5 for some reason. I know most times the chances on that happening are slim, but I think in the end its good practice.


It is not good approach to use as != 5. But

for (int i =0; i<index; ++i)

is more efficient than

for(int i=0; i<index; i++)

Because i++ first perform copy operation. For detailed information you can look operator overloading in C++.


Ultimately, the deciding factor as to what is more efficient is neither the language nor the compiler, but rather, the underlying hardware . If you're writing code for an embedded microcontroller like an 8051, counting up vs. counting down, greater or less than vs. not equals, and incrementing vs. decrementing, can make a difference to performance, within the very limited time scale of your loops.

While sufficient language and compiler support can (and often do) mitigate the absence of the instructions required to implement the specified code in an optimal but conceptually equivalent way, coding for the hardware itself guarantees performance, rather than merely hoping adequate optimizations exist at compile time.

And all this means, there is no one universal answer to your question, since there are so many different low-end microcontrollers out there.

Of much greater importance, however, than optimizing how your for loop iterates, loops, and breaks, is modifying what it does on each iteration . If causing the for loop one extra instruction saves two or more instructions within each iteration, do it ! You will get a net gain of one or more cycles! For truly optimal code, you have to weigh the consequences of fully optimizing how the for loop iterates over what happens on each iteration.

All that being said, a good rule of thumb is, if you would find it a challenge to memorize all the assembly instructions for your particular target hardware, the optimal assembly instructions for all variations of a “for” loop have probably been fully accounted for. You can always check if you REALLY care.


We can use one more trick for this.

for (i = 5; i > 0; i--)

I suppose most of the compilers optimize the loops like this. I am not sure. Someone please verify.


为了记录,“for”循环的cobol等价物是: -

    PERFORM VARYING VAR1 
            FROM +1 BY +1
            UNTIL VAR1 > +100
  *      SOME VERBOSE COBOL STATEMENTS HERE
    END-PERFORM.

要么

PERFORM ANOTHER-PARAGRAPH
        VARYING VAR2 BY +1 
        UNTIL TERMINATING-CONDITION
        WITH TEST AFTER.

这有很多变化。 对于长期暴露于COBOL而未受损的人们来说,主要的问题是,默认情况下, UNTIL实际上意味着WHILE即测试是在循环顶部执行的,在循环变量递增之前和之前是循环被处理。 你需要“ WITH TEST AFTER ”才能使它成为一个合适的UNTIL


似乎没有人说过为什么历史上preincrement运算符++i比后缀i++适用于小循环。

考虑前缀(increment和fetch)和后缀(fetch和increment)的典型实现:

// prefix form: increment and fetch
UPInt& UPInt::operator++()
{
   *this += 1;      // increment
   return *this;    // fetch
}

// posfix form: fetch and increment
const UPInt UPInt::operator++(int)
{
   const UPInt oldValue = *this;
   ++(*this);
   return oldValue;
} 

请注意,前缀操作可以就地完成,其中后缀需要另一个变量来跟踪旧值。 如果您不确定为什么会这样,请考虑以下事项:

int a = 0;
int b = a++; // b = 0, the old value, a = 1 

在一个小循环中,后缀所需的额外分配理论上可以使它更慢,因此旧学校逻辑是前缀更有效。 因此,许多C / C ++程序员都习惯使用前缀形式。

然而,其他地方指出的是现代编译器很聪明。 他们注意到在for循环中使用postfix表单时,不需要后缀的返回值。 因此,没有必要跟踪旧值并且可以对其进行优化 - 留下使用前缀表单获得的相同机器代码。


你给的东西实际上有四种排列。 对你们两个:

for(int i = 0; i < 5; i++)
for(int i = 0; i != 5; ++i)

我们可以添加:

for(int i = 0; i < 5; ++i)
for(int i = 0; i != 5; i++)

在大多数具有现代编译器的现代机器上,这些效率完全相同并不奇怪。 可能有一天你可能会发现自己为一些小型处理器编程,而平等比较和低于比较之间存在差异。

在某些情况下,根据我们选择0和5的原因,某个特定情况的特定情况可能更有意义地考虑“小于”或“不等于”,但即使这样,一个人看起来很明显编码员可能不会与另一个人。

更抽象的是,这些是以下形式:

for(someType i = start; i < end; i++)
for(someType i = start; i != end; ++i)
for(someType i = start; i < end; ++i)
for(someType i = start; i != end; i++)

这里一个明显的区别是,在两种情况下, someType必须具有<的含义,而对于其余部分,它必须具有!=的含义。 定义了!=并且<不常见的类型,包括C ++中的相当多的迭代器对象(可能在C#中,与STL迭代器相同的方法是可能的,有时是有用的,但都不是惯用的,直接由共同的图书馆也常常有用,因为有竞争对手的习语有更直接的支持)。 值得注意的是,STL方法是专门设计的,以便在有效迭代器类型集中包含指针。 如果您习惯使用STL,即使应用于整数,您也会考虑使用!=更加惯用的形式。 就个人而言,接触它的量非常小,足以让我成为我的直觉。

另一方面,虽然定义<而不是!=将是罕见的,但它适用于我们用i值的不同增加替换增量或者在循环内可以改变i情况。

因此,双方都有明确的案例,其中一种是另一种方法。

现在为++i vs i++ 。 再次使用整数并且直接调用而不是通过返回结果的函数(并且偶然的机会),实际结果将完全相同。

在一些C风格的语言(没有运算符过载的语言)中,整数和指针是唯一的情况。 我们可以人为地发明一种情况,即通过函数调用增量只是为了改变它的运行方式,编译器仍然可能会将它们变成同样的东西。

C ++和C#允许我们覆盖它们。 通常,前缀++运行方式类似于以下功能:

val = OneMoreThan(val);//whatever OneMoreThan means in the context.
//note that we assigned something back to val here.
return val;

postfix ++就像一个函数一样运行:

SomeType copy = Clone(val);
val = OneMoreThan(val);
return copy;

C ++和C#都不能完全匹配上面的内容(我故意不使我的伪代码匹配),但在任何一种情况下都可能有一个副本或者两个副本。 这可能贵也可能不贵。 它可能是也可能不是可以避免的(在C ++中我们通常可以通过返回void而在前缀形式中完全避免它)。 它可能会也可能不会被优化为任何东西,但在某些情况下,使用++i比使用i++更有效。

更具体地说,使用++i略微提高性能,并且使用大类甚至可能是相当大的,但是除非有人压倒C ++以使两者具有完全不同的含义(一个非常糟糕的主意)它不是一般来说,这可能是另一种方式。 因此,养成在postfix上使用前缀的习惯意味着你可能会在一千次中获得一次改进,但不会失败,所以这是C ++编码员经常遇到的习惯。

总之,在您的问题中给出的两种情况绝对没有区别,但可以有相同的变体。


关于使用++ i而不是i ++,它与大多数编译器没有区别,但是当用作迭代器时,++我可能比i ++更有效。


嗯...只要你不在你的for循环中修改i就没关系。 真正的“最佳”语法完全取决于您想要的结果。


在这些情况下关心效率并不是一个好主意,因为您的编译器通常足够聪明,可以在能够实现时优化代码。

我曾经为一家为安全关键系统生产软件的公司工作,其中一条规则是循环应该以“<”而不是!=结束。 有几个很好的理由:

  1. 您的控制变量可能会因某些问题或某些内存入侵而跳转到更高的值;

  2. 在维护中,可以在循环内增加迭代器值,或者执行类似“i + = 2”的操作,这将使循环永久滚动;

  3. 如果由于某种原因,你的迭代器类型从“int”变为“float”(我不知道为什么有人会这样做,但无论如何......)浮点数的精确比较是一种不好的做法。

(MISRA C ++编码标准(用于安全关键系统)也告诉你在规则6-5-2中更喜欢“<”而不是“!=”。我不知道我是否可以在这里发布规则定义因为MISRA是付费文件。)

关于++ i或i ++,我更愿意使用++ i。 当您使用基本类型时,没有区别,但是当您使用STL迭代器时,preincrement更有效。 所以我总是使用preincrement来适应它。


在阅读了Dijkstra的一本名为“编程学科”的书之后,我在20多年前转而使用!= 。 在他的书中,Dijkstra观察到较弱的延续条件导致环结构中更强的后置条件。

例如,如果我们修改你的构造以在循环之后暴露i ,则第一个循环的后置条件将是i >= 5 ,而第二个循环的后置条件是更强的i == 5 。 这对于循环不变量,后置条件和最弱前提条件的形式化程序的推理更好。


如果你的索引不是一个int ,而是(比方说)一个C ++类,那么第二个例子就有可能更有效率。

但是,正如所写,你认为第二种形式更有效的信念是完全错误的。 任何体面的编译器都会有一个简单的for循环的优秀的codegen惯用语,并且可以为这两个例子生成高质量的代码。 更重要的是:

  • 在一个正在进行重度性能关键计算的for循环中,索引算法几乎可以忽略整个负载。

  • 如果你的for循环对性能至关重要而且没有进行繁重的计算,这样索引算法实际上很重要,你几乎肯定会重构你的代码,以便在循环的每次传递中做更多的工作。


如果由于某种原因i在循环中跳到50,你的版本将永远循环。 i < 5是理智检查。


总结两种选择的利弊

优点!=

  • 当int被一些迭代器替换或者通过模板参数传递的类型时,它更有可能工作,它会做预期的事情并且会更有效率。
  • 如果i变量发生了不可预测的事情,允许错误检测,它将“永远循环”

<的优点

  • 正如其他人所说,与其他简单类型的效率一样高效
  • 如果我在循环中增加或者用循环运行时被修改的某个表达式替换5,它将不会'永久'运行
  • 将使用浮点类型
  • 更具可读性 - 习惯的问题

我的结论:

  1. 也许在大多数情况下都应该使用!=版本,当i是离散的时候,并且比较的另一侧也不应该在循环内被篡改。

  2. 虽然<的存在将是一个明确的信号,即i是简单类型(或评估为简单类型) 并且条件不简单:在循环和/或并行处理中另外修改i或条件。


我同意关于可读性的说法 - 重要的是让代码对于维护者来说很容易阅读,尽管你希望无论是谁,都能理解增量前后的内容。

也就是说,我认为我会进行一个简单的测试,并获得一些关于四个循环中哪个循环运行最快的可靠数据。 我是普通的规格电脑,用javac 1.7.0编译。

我的程序进行了一个for循环,在没有任何东西的情况下迭代了2,000,000次(以免在for循环中做任何事情需要多长时间来淹没有趣的数据)。 它使用上面提出的所有四种类型,并对结果进行计时,重复1000次以获得平均值。

实际代码是:

public class EfficiencyTest
{
public static int iterations = 1000;

public static long postIncLessThan() {
    long startTime = 0;
    long endTime = 0;
    startTime = System.nanoTime();
    for (int i=0; i < 2000000; i++) {}
    endTime = System.nanoTime();
    return endTime - startTime;
}

public static long postIncNotEqual() {
    long startTime = 0;
    long endTime = 0;
    startTime = System.nanoTime();
    for (int i=0; i != 2000000; i++) {}
    endTime = System.nanoTime();
    return endTime - startTime;
}

public static long preIncLessThan() {
    long startTime = 0;
    long endTime = 0;
    startTime = System.nanoTime();
    for (int i=0; i < 2000000; ++i) {}
    endTime = System.nanoTime();
    return endTime - startTime;
}

public static long preIncNotEqual() {
    long startTime = 0;
    long endTime = 0;
    startTime = System.nanoTime();
    for (int i=0; i != 2000000; ++i) {}
    endTime = System.nanoTime();
    return endTime - startTime;
}

public static void analyseResults(long[] data) {
    long max = 0;
    long min = Long.MAX_VALUE;
    long total = 0;
    for (int i=0; i<iterations; i++) {
        max = (max > data[i]) ? max : data[i];
        min = (data[i] > min) ? min : data[i];
        total += data[i];
    }
    long average = total/iterations;

    System.out.print("max: " + (max) + "ns, min: " + (min) + "ns");
    System.out.println("\tAverage: " + (average) + "ns");
}

public static void main(String[] args) {
    long[] postIncLessThanResults = new long [iterations];
    long[] postIncNotEqualResults = new long [iterations];
    long[] preIncLessThanResults = new long [iterations];
    long[] preIncNotEqualResults = new long [iterations];

    for (int i=0; i<iterations; i++) {
        postIncLessThanResults[i] = postIncLessThan();
        postIncNotEqualResults[i] = postIncNotEqual();
        preIncLessThanResults[i] = preIncLessThan();
        preIncNotEqualResults[i] = preIncNotEqual();
    }
    System.out.println("Post increment, less than test");
    analyseResults(postIncLessThanResults);

    System.out.println("Post increment, inequality test");
    analyseResults(postIncNotEqualResults);

    System.out.println("Pre increment, less than test");
    analyseResults(preIncLessThanResults);

    System.out.println("Pre increment, inequality test");
    analyseResults(preIncNotEqualResults);
    }
}

对不起,如果我复制错了!

结果让我觉得 - 测试i < maxValue每个循环大约需要1.39ms,无论是使用前增量还是后增量,但i != maxValue需要1.05ms。 这是一个节省24.5%或32.5%的时间,取决于你如何看待它。

当然,for循环运行所需的时间可能不是你的瓶颈,但这是一种有用的优化,对于你需要它的罕见情况。

不过我认为我仍然坚持不到测试!

编辑

我已经测试过递减i,并且发现这for (int i = 2000000; i != 0; i--)for (int i = 0; i != 2000000; i++)两者都采用相同的时间长度,如for (int i = 2000000; i > 0; i--)for (int i = 0; i < 2000000; i++)


我永远不会这样做:

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

i!= 5让它开放的可能性我将永远不会是5.它太容易跳过它并遇到无限循环或数组访问器错误。

++i

虽然很多人都知道你可以把++放在前面,但是很多人都没有。 代码需要对人们可读,虽然它可能是一个微优化,以使代码更快,但当有人必须修改代码并说明为什么要完成时,真的不值得额外的头痛。

我认为道格拉斯·克罗克福德有最好的建议,那就是不要使用++或者 - 根本不是。 它有时会变得太混乱(可能不是在循环中,但绝对是在其他地方)并且它很容易写i = i + 1.他认为这是一个坏习惯,离开,我有点看到一些残酷的“优化”代码后同意。

我认为crockford所面临的是那些运营商,你可以让人们写下这样的东西:

var x = 0;
var y = x++;

y = ++x * (Math.pow(++y, 2) * 3) * ++x;

alert(x * y);

//答案是54顺便说一句。


第二个是不太可读,我想(如果只是因为“标准”实践似乎是前者)。


表格

for (int i = 0; i < 5; i++)

惯用的 ,所以对于经验丰富的C程序员来说,它更容易阅读。 特别是当用于迭代数组时。 您应尽可能编写惯用代码,因为它读取速度更快。

当你在循环中修改i或使用不同于1的增量时,它也会更安全一些。但这是一个小问题。 最好仔细设计你的循环并添加一些断言以尽早捕捉破坏的假设。





for-loop