i++和++i的那些陷阱坑

一般意义上的理解,++i是先定义一个i的副本,将i执行+1,最后返回那个副本; i++是将i执行+1,然后返回i的引用。
前缀版本++i和后缀版本i++,他们对操作数的影响是一样的,但是影响的时间不同。这就像对于钱包来说,清理草坪之前付钱和清理草坪之后付钱的最终结果是一样的,但支付钱的时间不同。(《C++Primer Plus》5.1)


接下来用两个代码例子说明一下:
例1:

#include <stdio.h>  
int main()  
{  
    int i=0;  
    printf("%d,%d,%d,%d\n",i++,--i,++i,i--);  

    return 0;  
}  
    按照最初的理解,前置操作是将i放在寄存器中,然后进行操作,最后通过寄存器返回;后置是将值放在临时量中,输出是直接从临时量中取值。函数运行需要从右往左向栈中压入参数,所以猜想结果是0,-1,-1,-1。

但是编译器输出是-1,0,0,0
先不看结果如何,应该想一想这个操作执行的过程。
接下来,从反汇编代码看一看

    int i=0;  
0120437E  mov         dword ptr [i],0           //把0赋值给i
    printf("%d,%d,%d,%d\n",i++,--i,++i,i--);    //参数从右向左压栈
01204385  mov         eax,dword ptr [i]         //把i的值放在寄存器eax
01204388  mov         dword ptr [ebp-0D0h],eax  //把寄存器中的值放在ebp-0D0h位置处的临时内存区域(临时量)    

0120438E  mov         ecx,dword ptr [i]         //把i的值放在ecx中 
01204391  sub         ecx,1                     //寄存器ecx中的值-1
01204394  mov         dword ptr [i],ecx         //ecx中取出i的值  
01204397  mov         edx,dword ptr [i]         //把i的值放在寄存器edx中
0120439A  add         edx,1                     //寄存器edx中的值+1
0120439D  mov         dword ptr [i],edx         //将edx中的值放在i的存储区域中,更新++i   

012043A0  mov         eax,dword ptr [i]         //把i的值放进寄存器eax
012043A3  sub         eax,1                     //对eax中的值-1
012043A6  mov         dword ptr [i],eax         //将eax中的值放在i的存储区域中,更新i--
012043A9  mov         ecx,dword ptr [i]         //把i的值放在寄存器ecx中
    printf("%d,%d,%d,%d\n",i++,--i,++i,i--);  
012043AC  mov         dword ptr [ebp-0D4h],ecx  //把寄存器中的值放在ebp-0D0h位置处的临时内存区域(临时量)
012043B2  mov         edx,dword ptr [i]         //把i的值放在edx中
012043B5  add         edx,1                     //对edx中的值+1
012043B8  mov         dword ptr [i],edx         //将edx中值放在i的存储区域中,更新i++

012043BB  mov         esi,esp  
012043BD  mov         eax,dword ptr [ebp-0D0h]   //参数的值确定好了之后,开始进行压栈操作。    将临时区域中的值放在eax中
012043C3  push        eax                       //将 i-- 的结果压栈。
012043C4  mov         ecx,dword ptr [i]         //到i的区域取值;
012043C7  push        ecx                       //++i
012043C8  mov         edx,dword ptr [i]         //前置操作是到自己的存储区域取值
012043CB  push        edx                       //--i
012043CC  mov         eax,dword ptr [ebp-0D4h]  
012043D2  push        eax                       //i++  取出临时区域的值
012043D3  push        120CC6Ch  
012043D8  call        dword ptr ds:[12103B8h]  
012043DE  add         esp,14h  
012043E1  cmp         esi,esp  
012043E3  call        __RTC_CheckEsp (012012D0h)  

看完汇编代码过程,应该明白它的结果了。
将后置++/–的值放在了一个临时区域内(而不是用寄存器存),然后再对变量的存储区域进行+1/-1。 而前置++/–的值直接经过计算将结果存入到变量的存储区域内。


例2:简单实现了一个Int类

在C++中,我们对自定义的类实现重载操作符,需要满足内置类型一样的操作,例如前置操作和后置操作。

class Int
{
public:
    Int(int val)
    {
        _val = val;
    }
    Int& operator++()//前置++
    {
        ++_val;
        return *this;
    }
    const Int operator++(int)//后置++
    {
        Int tmp = *this;
        ++(*this);//利用前置++
        return tmp;
    }
private:
    int _val;
};

在实现前置++时,直接进行++,在自己的存储区域取值,返回*this;
后置++因为是先赋值在++,所以要返回自增前的对象,先拷贝一份然后自增,返回拷贝的对象。

看到这里,不由地想到几个问题或者是疑问。
1.后置++重载为什么要加一个参数int ??

    构成重载的条件之一是:在同一作用域中,函数名相同,参数列表不同。
    这里前置++和后置++的重载实现,因为在相同作用域下,函数名相同,多加一个参数int是为了重载实现,绕过语法限制。

2.前置++为什么返回类型是引用?后置++为什么返回const对象??

返回引用是:(1)去除临时量;(2)可能会赋值

    后置++如果不是const,当编译器遇到i(++)++时也会让它通过运行,理论上应该是i只进行了+1,第二次++是一个临时量的自增。这与内置类型发生矛盾,内置类型不支持i(++)++。
    自定义的类型重载,应该与内置类型保持一致。

((3.还有一点是,两者在效率上的区别?

    后置操作,每次都构造临时对象并返回,会产生临时对象的构造和析构,而且后置中利用前置操作实现。
    所以,我们在很多代码中看到,前置++/--的影子,因为前置操作的效率更高。
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值