一般意义上的理解,++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.还有一点是,两者在效率上的区别?
后置操作,每次都构造临时对象并返回,会产生临时对象的构造和析构,而且后置中利用前置操作实现。
所以,我们在很多代码中看到,前置++/--的影子,因为前置操作的效率更高。