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.還有一點是,兩者在效率上的區別?

    後置操作,每次都構造臨時對象並返回,會產生臨時對象的構造和析構,而且後置中利用前置操作實現。
    所以,我們在很多代碼中看到,前置++/--的影子,因爲前置操作的效率更高。
發佈了40 篇原創文章 · 獲贊 6 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章