我们的问题是,在C语言中i++和++i的性能有区别吗?

c++的答案是什么?


当前回答

@Mark:我删除了我之前的答案,因为它有点轻率,仅凭这一点就应该被打反对票。实际上,我认为这是一个很好的问题,因为它问了很多人的想法。

通常的答案是++i比i++快,毫无疑问,但更大的问题是“什么时候应该关心?”

如果增量迭代器所花费的CPU时间小于10%,那么您可能不会在意。

如果增量迭代器所花费的CPU时间百分比大于10%,则可以查看哪些语句正在进行该迭代。看看你是否可以只增加整数而不是使用迭代器。你有可能做到,虽然在某种意义上可能不太理想,但很有可能你会节省花在那些迭代器上的所有时间。

我曾经见过一个例子,其中迭代器增量消耗了超过90%的时间。在这种情况下,采用整数递增法将执行时间减少了这么多。(即优于10倍加速)

其他回答

++i比i++快,因为它不返回值的旧副本。

它也更直观:

x = i++;  // x contains the old value of i
y = ++i;  // y contains the new value of i 

这个C语言的例子输出的是“02”而不是你所期望的“12”:

#include <stdio.h>

int main(){
    int a = 0;
    printf("%d", a++);
    printf("%d", ++a);
    return 0;
}

c++也是一样:

#include <iostream>
using namespace std;

int main(){
    int a = 0;
    cout << a++;
    cout << ++a;
    return 0;
}

说编译器不能优化掉后缀情况下的临时变量副本是不完全正确的。用VC进行的快速测试表明,至少在某些情况下,它可以做到这一点。

在下面的例子中,生成的代码对于前缀和后缀是相同的,例如:

#include <stdio.h>

class Foo
{
public:

    Foo() { myData=0; }
    Foo(const Foo &rhs) { myData=rhs.myData; }

    const Foo& operator++()
    {
        this->myData++;
        return *this;
    }

    const Foo operator++(int)
    {
        Foo tmp(*this);
        this->myData++;
        return tmp;
    }

    int GetData() { return myData; }

private:

    int myData;
};

int main(int argc, char* argv[])
{
    Foo testFoo;

    int count;
    printf("Enter loop count: ");
    scanf("%d", &count);

    for(int i=0; i<count; i++)
    {
        testFoo++;
    }

    printf("Value: %d\n", testFoo.GetData());
}

无论您使用的是++testFoo还是testfoo++,都将得到相同的结果代码。事实上,无需从用户读取计数,优化器将整个事情归结为一个常数。所以这个:

for(int i=0; i<10; i++)
{
    testFoo++;
}

printf("Value: %d\n", testFoo.GetData());

结果如下:

00401000  push        0Ah  
00401002  push        offset string "Value: %d\n" (402104h) 
00401007  call        dword ptr [__imp__printf (4020A0h)] 

因此,虽然后缀版本肯定会更慢,但如果你不使用它,优化器可能会足够好,可以摆脱临时副本。

是的。有。

++操作符可以定义为函数,也可以不定义为函数。对于基本类型(int, double,…),操作符是内置的,因此编译器可能能够优化您的代码。但对于定义了++运算符的对象,情况就不一样了。

操作符++(int)函数必须创建一个副本。这是因为postfix ++被期望返回一个与它所保存的值不同的值:它必须将其值保存在一个临时变量中,自增其值并返回临时值。在操作符++(),前缀++的情况下,不需要创建一个副本:对象可以自增,然后简单地返回自己。

下面是关于这一点的一个例子:

struct C
{
    C& operator++();      // prefix
    C  operator++(int);   // postfix

private:

    int i_;
};

C& C::operator++()
{
    ++i_;
    return *this;   // self, no copy created
}

C C::operator++(int ignored_dummy_value)
{
    C t(*this);
    ++(*this);
    return t;   // return a copy
}

每次调用操作符++(int)都必须创建一个副本,编译器对此无能为力。当有选择时,使用运算符++();这样就不需要保存副本。在很多增量(大循环?)和/或大对象的情况下,它可能很重要。

谷歌c++风格指南说:

Preincrement and Predecrement Use prefix form (++i) of the increment and decrement operators with iterators and other template objects. Definition: When a variable is incremented (++i or i++) or decremented (--i or i--) and the value of the expression is not used, one must decide whether to preincrement (decrement) or postincrement (decrement). Pros: When the return value is ignored, the "pre" form (++i) is never less efficient than the "post" form (i++), and is often more efficient. This is because post-increment (or decrement) requires a copy of i to be made, which is the value of the expression. If i is an iterator or other non-scalar type, copying i could be expensive. Since the two types of increment behave the same when the value is ignored, why not just always pre-increment? Cons: The tradition developed, in C, of using post-increment when the expression value is not used, especially in for loops. Some find post-increment easier to read, since the "subject" (i) precedes the "verb" (++), just like in English. Decision: For simple scalar (non-object) values there is no reason to prefer one form and we allow either. For iterators and other template types, use pre-increment.

@wilhelmtell

编译器可以省略临时对象。从另一个线程逐字逐句:

c++编译器允许消除基于堆栈的临时对象,即使这样做会改变程序行为。MSDN链接vc8:

http://msdn.microsoft.com/en-us/library/ms364057 (VS.80) . aspx