一般意義上的理解,++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.還有一點是,兩者在效率上的區別?
後置操作,每次都構造臨時對象並返回,會產生臨時對象的構造和析構,而且後置中利用前置操作實現。
所以,我們在很多代碼中看到,前置++/--的影子,因爲前置操作的效率更高。