C/C++中自增自减的前置和后置区别

本文探讨了C++中自增自减操作符的使用及其性能差异,通过实验验证了前置与后置操作符的不同之处,并针对迭代器类型进行了特别分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       自增自减操作符在编程中很常用,都分为分为前置和后置两种操作符,对于两者的区别,C++ primer中有着如下说明:

       1. 前置操作返回的结果为左值,后置操作返回的是右值,也就是说执行如下的代码都会编译不通过:

int i = 0,  j = 0;
i++  =  j;
cout  <<  i++++  <<  endl  <<  ++i--;

       2. 前置操作符相比后置操作符所做的工作要少,就是对其操作是执行加1或减1操作,在原操作数上进行,也就是++i等价于i  +=  1; 后置操作符则必须先保存操作数原来的值,以便返回未加1之前的值作为操作的结果,因此,建议用前置操作符,对于int型对象和指针,编译器可优化掉这项额外工作,但是对于更多的复杂迭代器类型,这种额外的工作可能会花费更大的代价,因此,养成使用前置操作的习惯,就不必操心性能差异的问题了。

       我对上述两点进行了测试,第一种很容易验证,但是第二点就出了些小问题,下面一一道来:

       为了更深入地测试,我观察了汇编代码。

       在codeblocks中进行测试,如下两行代码:

++i;
i--;
生成的汇编代码一致,都为incl  0xc(%esp),也就是单独写在一行时,编译器会进行优化,此时两者的效率是一样的。对于double,long等等内置类型进行测试,都会进行优化。这就验证了C++ Primer中所说,并没有网上一些人说的,后置操作符要多占一个临时空间,但毕竟这是两个单独的语句,进行进一步的测试,对于下面的代码:

int i = 3;
int j = 0;
j = ++i;
j = ++i;对应的汇编代码为

incl  0xc(%esp)

mov  0xc(%esp), %eax

mov  %eax, 0x8(%esp)

如果将j = ++i;换为j = i++;则对应的汇编代码为

mov  0xc(%esp), %eax

mov  %eax, 0x8(%esp)

incl  0xc(%esp)

两者对比下,所做的工作基本一致,就是顺序不同,都要用到eax寄存器,没有出现前置比后置操作符要效率高的表现,也没有出现网上所说的额外的存储空间的问题。符合C++ Primer上所说的编译器会进行优化。编译器对内置类型会进行极大的优化,比如j  = ++++i;对应的汇编代码为

incl   0xc(%esp)
incl   0xc(%esp)
mov    0xc(%esp),%eax
mov    %eax,0x8(%esp)

直接进行两次自增,而不会先算出++i,然后再算++(++i)。

下面进行进一步的测试,对复杂的迭代器类型,这次编译器可能对优化有心无力了。因为迭代器生成的汇编代码有些复杂,所以换一种方式进行测试,代码如下:

    clock_t start = 0, finish = 0, duration = 0;
    vector<int> *v = new vector<int>();
    v->assign(1000000, 2345);
    vector<int>::iterator iter = v->begin();

    //运行5000次
    for (int i=0; i<5000; ++i)
    {
        start = clock();
        for (; iter != v->end(); ++iter);
        finish = clock();
        duration += finish - start;
    }

    cout << "总运行时间为:" << duration  << "毫秒" << endl;
最后运行结果:++iter的平均时间为:24毫秒
                         iter++的平均时间为:31毫秒

基本上可以验证书中所说,所以平时要进行for循环时,养成用前置的习惯比较好





### C++ 前置后置区别 #### 定义行为差异 在 C++ 中,`++i` 表示前置操作符,而 `i++` 则表示后置操作符。两者的共同点是对变量执行加一的操作,但在具体的行为上存在显著区别。 对于前置 (`++i`),它会先对变量进行自操作,然后再返回该变量的值[^1]。这意味着,在表达式中使用 `++i` 时,所得到的结果已经是经过自之后的新值。 而对于后置 (`i++`),它的特点是先返回当前变量的原始值,随后再对该变量执行自操作。因此,在表达式中使用 `i++` 的情况下,所得结果将是未被修改之前的旧值。 #### 效率上的考虑 另一个重要的不同之处在于性能方面。由于后置需要保存原值以便后续返回给调用者,这通常涉及创建一个临时对象来存储这个初始状态。相比之下,前置不需要这样的额外开销,因为它直接返回已更新的对象本身。这种机制使得前置往往比后置更高效,尤其是在处理复杂数据类型或者大型类实例的时候。 #### 运算符重载中的实现细节 当涉及到运算符重载时,前置后置也有各自独特的定义方式: - **前置**可以通过成员函数的形式简单地完成,如下所示: ```cpp Counter &operator++() { count++; return *this; } ``` 此处通过加内部计数器并返回本对象的引用实现了预期功能[^2]。 - **后置**则相对复杂一些,因为除了要提升自身的数值外还需要提供原来的副本作为结果输出: ```cpp Counter operator++(int) { Counter temp(*this); count++; return temp; } ``` 上述代码片段展示了如何利用参数列表里的整型占位符区分两种形式,并借助局部变量暂存原有的状况直至最终交付出去。 #### 实际应用案例解析 下面给出一段具体的程序演示上述理论知识点的实际效果: ```cpp #include <iostream> using namespace std; int main(){ int i = 0, m=0; int j = i++; // 使用后置 int k = ++m; // 使用前置 cout << "i="<< i << "\tj=" << j << endl; cout << "m="<< m << "\tk=" << k << endl; return 0; } /* 输出结果为: i=1 j=0 m=1 k=1 */ ``` 从运行结果可以看出,采用后置的情况下(`i++`),虽然最后`i`确实变成了1,但是分配给`j`的那个瞬间还是保持了原来的状态也就是零;相反地,运用前置(`++m`)就立即反映了变动情况——既改变了源变量又把新的数值赋予目标位置[^3]。 #### 返回类型的特性说明 另外值得注意的一点就是有关它们各自的返回性质: - 前置(++x),其返回的是变量 x 自后的左值(lvalue), 即指向内存地址可再次参赋值等动作; - 而后置(x++),仅能传递回右值(rvalue),代表不可进一步更改或定位的具体数值而已[^4]。 ### 结论 综上所述,无论是基本原理层面还是实际编码实践当中,C++里边的前置后置之间都存在着本质性的差别。理解这些细微却至关重要的概念有助于编写更加精确高效的软件解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值